* feat: adding support for legacy XML log output (#119)

* Custom Log Format  (#124)

* Make JSON the default log format

---------

Co-authored-by: Charity Kathure <ckathure@microsoft.com>

* Custom JSON Log Sanitization (#128)

---------

Co-authored-by: Charity Kathure <ckathure@microsoft.com>

* Formatting for scalar integer property values using TdhFormatProperty (#129)

---------

Co-authored-by: Bob Sira <bosira@microsoft.com>

* docs: configurable / custom log format (#136)

---------

Co-authored-by: Charity Kathure <ckathure@microsoft.com>

* fix process monitor formatting (#175)

---------

Co-authored-by: Charity Kathure <ckathure@microsoft.com>

* Process Monitor Custom Logging (#176)

Signed-off-by: Charity Kathure <ckathure@microsoft.com>

---------

Signed-off-by: Charity Kathure <ckathure@microsoft.com>
Co-authored-by: Charity Kathure <ckathure@microsoft.com>

* Resolve build error and lint issues, and add process monitor details into the docs

Signed-off-by: Charity Kathure <ckathure@microsoft.com>

---------

Signed-off-by: Charity Kathure <ckathure@microsoft.com>
Co-authored-by: Charity Kathure <ckathure@microsoft.com>
Co-authored-by: Bob Sira <sbobfitz2@gmail.com>
Co-authored-by: Bob Sira <bosira@microsoft.com>
Co-authored-by: Ian King'ori <kingorim.ian@gmail.com>
This commit is contained in:
Charity Kathure 2024-07-24 14:45:42 +03:00 коммит произвёл GitHub
Родитель c52490550b
Коммит d1d39d6a31
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
20 изменённых файлов: 817 добавлений и 110 удалений

Просмотреть файл

@ -1712,5 +1712,48 @@ namespace LogMonitorTests
);
}
}
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%', 'Logline':'%Logline%'}";
{
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());
}
}
};
}

Просмотреть файл

@ -74,7 +74,7 @@ namespace LogMonitorTests
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
fflush(stdout);
EtwMonitor etwMonitor(etwProviders, true);
EtwMonitor etwMonitor(etwProviders, L"json", L"");
Sleep(WAIT_TIME_ETWMONITOR_START);
@ -168,7 +168,7 @@ namespace LogMonitorTests
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
fflush(stdout);
EtwMonitor etwMonitor(etwProviders, true);
EtwMonitor etwMonitor(etwProviders, L"json", L"");
//
// It must find the provider, and start printing events.
@ -203,7 +203,7 @@ namespace LogMonitorTests
fflush(stdout);
std::function<void(void)> f1 = [&etwProviders] { EtwMonitor etwMonitor(etwProviders, true); };
std::function<void(void)> f1 = [&etwProviders] { EtwMonitor etwMonitor(etwProviders, L"json", L""); };
Assert::ExpectException<std::invalid_argument>(f1);
}
@ -223,7 +223,7 @@ namespace LogMonitorTests
providerWithoutLevel.Keywords = 0;
std::vector<ETWProvider> etwProviders = { providerWithLevel, providerWithoutLevel };
EtwMonitor etwMonitor(etwProviders, true);
EtwMonitor etwMonitor(etwProviders, L"json", L"");
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
fflush(stdout);

Просмотреть файл

@ -103,7 +103,7 @@ namespace LogMonitorTests
{
std::vector<EventLogChannel> eventChannels = { {L"Application", EventChannelLogLevel::Error} };
EventMonitor eventMonitor(eventChannels, true, false);
EventMonitor eventMonitor(eventChannels, true, false, L"json", L"");
Sleep(WAIT_TIME_EVENTMONITOR_START);
//
@ -209,7 +209,7 @@ namespace LogMonitorTests
{
std::vector<EventLogChannel> eventChannels = { {L"Application", EventChannelLogLevel::Information} };
EventMonitor eventMonitor(eventChannels, true, false);
EventMonitor eventMonitor(eventChannels, true, false, L"json", L"");
Sleep(WAIT_TIME_EVENTMONITOR_START);
//
@ -356,7 +356,7 @@ namespace LogMonitorTests
Assert::AreEqual(0, WriteEvent(level, eventId, message));
EventMonitor eventMonitor(eventChannels, true, true);
EventMonitor eventMonitor(eventChannels, true, true, L"json", L"");
Sleep(WAIT_TIME_EVENTMONITOR_START);
{
@ -382,7 +382,7 @@ namespace LogMonitorTests
{
std::vector<EventLogChannel> eventChannels = { {L"System", EventChannelLogLevel::Information} };
EventMonitor eventMonitor(eventChannels, false, false);
EventMonitor eventMonitor(eventChannels, false, false, L"json", L"");
Sleep(WAIT_TIME_EVENTMONITOR_START);
{

Просмотреть файл

@ -176,7 +176,7 @@ namespace LogMonitorTests
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds);
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L"");
Sleep(WAIT_TIME_LOGFILEMONITOR_START);
//
@ -224,7 +224,7 @@ namespace LogMonitorTests
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds);
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L"");
Sleep(WAIT_TIME_LOGFILEMONITOR_START);
//
@ -293,7 +293,7 @@ namespace LogMonitorTests
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds);
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L"");
Sleep(WAIT_TIME_LOGFILEMONITOR_START);
//
@ -396,7 +396,7 @@ namespace LogMonitorTests
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds);
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L"");
Sleep(WAIT_TIME_LOGFILEMONITOR_START);
//
@ -572,7 +572,7 @@ namespace LogMonitorTests
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds);
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L"");
Sleep(WAIT_TIME_LOGFILEMONITOR_START);
//
@ -678,7 +678,7 @@ namespace LogMonitorTests
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds);
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L"");
Sleep(WAIT_TIME_LOGFILEMONITOR_START);
//
@ -798,7 +798,7 @@ namespace LogMonitorTests
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds);
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L"");
Sleep(WAIT_TIME_LOGFILEMONITOR_START);
//
@ -989,7 +989,7 @@ namespace LogMonitorTests
fflush(stdout);
ZeroMemory(bigOutBuf, sizeof(bigOutBuf));
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds);
std::shared_ptr<LogFileMonitor> logfileMon = std::make_shared<LogFileMonitor>(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L"");
Sleep(WAIT_TIME_LOGFILEMONITOR_START);
//

Просмотреть файл

@ -6,6 +6,7 @@
- [Event Log Monitoring](#event-log-monitoring)
- [Log File Monitoring](#log-file-monitoring)
- [Process Monitoring](#process-monitoring)
- [Log Format Customization](#log-format-customization)
## Sample Config File
@ -366,3 +367,114 @@ CMD "c:\\windows\\system32\\ping.exe -n 20 localhost"
```
The Process Monitor will stream the output for `c:\windows\system32\ping.exe -n 20 localhost`
## Log Format Customization
### Description
By default, logs will be displayed in JSON format. However, users can change the log format to either `XML` or their own `custom` defined format.
To specify the log format, a user needs to configure the `logFormat` field in `LogMonitorConfig.json` to either `XML`, `JSON` or `Custom` <em>(the field value is not case-insensitive)</em>
<br>For `JSON` and `XML` log formats, no additional configurations are required. However, the `Custom` log format, needs further configuration. For custom log formats, a user needs to specify the `customLogFormat` at the source level.
### Custom Log Format Pattern Layout
To ensure the different field values are correctly displayed in the customized log outputs, ensure to wrap the field names within modulo operators (%) and the field names specified matches the correct log sources' field names.
For example: `%Message%, %TimeStamp%`<br>
Each log source tracked by log monitor <em>(ETW, Log File, Events, and Process Monitor logs)</em> has log field names specific to them:
<strong>Event Logs:</strong>
- `Source`: The log source (Event Log)
- `TimeStamp`: Time at which the event was generated
- `EventID`: Unique identifier assigned to an individual event
- `Severity`: A label that indicates the importance or criticality of an event
- `Message`: The event message
<strong>ETW:</strong>
- `Source`: The log source (ETW)
- `TimeStamp`: Time at which the event was generated
- `Severity`: A label that indicates the importance or criticality of an event
- `ProviderId`: Unique identifier that is assigned to the event provider during its registration process.
- `ProviderName`: Unique identifier or name assigned to an event provider
- `DecodingSource`: Component or provider responsible for decoding and translating raw event data into a human-readable format
- `ExecutionProcessId`: Identifier associated with a process that is being executed at the time an event is generated
- `ExecutionThreadId`: Identifier associated with a thread at the time an event is generated
- `Keyword`: Flag or attribute assigned to an event or a group of related events
- `EventId`: Unique identifier assigned to an individual event
- `EventData`: Payload or data associated with an event.
<strong>Log Files:</strong>
- `Source`: The log source (File)
- `TimeStamp`: Time at which the change was introduced in the monitored file.
- `FileName`: Name of the file that the log entry is read from.
- `Message`: The line/change added in the monitored file.
<strong>Process Monitor:</strong>
- `Source`: The log source (Process Monitor)
- `TimeStamp`: Time at which the process was executed
- `Logline` or `logEntry` : The output of the process/command executed
### Sample Custom Log Configuration
```json
{
"LogConfig": {
"logFormat": "custom",
"sources": [
{
"type": "ETW",
"eventFormatMultiLine": false,
"providers": [
{
"providerName": "Microsoft-Windows-WLAN-Drive",
"providerGuid": "DAA6A96B-F3E7-4D4D-A0D6-31A350E6A445",
"level": "Information"
}
],
"customLogFormat": "{'TimeStamp':'%TimeStamp%', 'source':'%Source%', 'Severity':'%Severity%', 'ProviderId':'%ProviderId%', 'ProviderName':'%ProviderName%', 'EventId':'%EventId%', 'EventData':'%EventData%'}"
},
{
"type": "File",
"directory": "c:\\inetpub\\logs",
"filter": "*.log",
"includeSubdirectories": true,
"customLogFormat": "{'message':%Message%,'source':%Source%,'fileName':%FileName%}"
},
{
"type": "Process",
"customLogFormat": "{'TimeStamp':'%TimeStamp%', 'source':'%Source%', 'Logline':'%Logline%'}"
}
]
}
}
```
For advanced usage of the custom log feature, a user can choose to define their own custom JSON log format. In such a case, The `logFormat` value should be `custom`.
<br>To enable sanitization of the JSON output and ensure the the outputs displayed by the tool is valid, the user can add a suffix: `'|json'` after the desired custom log format.
For example:
```json
{
"LogConfig": {
"logFormat": "custom",
"sources": [
{
"type": "ETW",
"eventFormatMultiLine": false,
"providers": [
{
"providerName": "Microsoft-Windows-WLAN-Drive",
"providerGuid": "DAA6A96B-F3E7-4D4D-A0D6-31A350E6A445",
"level": "Information"
}
],
"customLogFormat": "{'TimeStamp':'%TimeStamp%', 'source':'%Source%', 'Severity':'%Severity%', 'ProviderId':'%ProviderId%', 'ProviderName':'%ProviderName%', 'EventId':'%EventId%', 'EventData':'%EventData%'}|json"
},
{
"type": "Process",
"customLogFormat": "{'TimeStamp':'%TimeStamp%', 'source':'%Source%', 'Logline':'%Logline%'}|JSON"
}
]
}
}
```

Просмотреть файл

@ -140,9 +140,10 @@ ReadLogConfigObject(
bool sourcesTagFound = false;
if (Parser.BeginParseObject())
{
std::wstring key;
do
{
const std::wstring key(Parser.GetKey());
key = Parser.GetKey();
if (_wcsnicmp(key.c_str(), JSON_TAG_SOURCES, _countof(JSON_TAG_SOURCES)) == 0)
{
@ -188,6 +189,10 @@ ReadLogConfigObject(
}
} 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());
@ -306,6 +311,7 @@ ReadSourceAttributes(
// These attributes are string type
// * directory
// * filter
// * lineLogFormat
//
else if (_wcsnicmp(key.c_str(), JSON_TAG_DIRECTORY, _countof(JSON_TAG_DIRECTORY)) == 0)
{
@ -313,7 +319,8 @@ ReadSourceAttributes(
FileMonitorUtilities::ParseDirectoryValue(directory);
Attributes[key] = new std::wstring(directory);
}
else if (_wcsnicmp(key.c_str(), JSON_TAG_FILTER, _countof(JSON_TAG_FILTER)) == 0)
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());
}
@ -648,6 +655,21 @@ AddNewSource(
break;
}
case LogSourceType::Process:
{
std::shared_ptr<SourceProcess> 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<LogSource>(std::move(sourceProcess)));
break;
}
}
return true;
}

Просмотреть файл

@ -30,9 +30,11 @@ static const std::wstring g_sessionName = L"Log Monitor ETW Session";
EtwMonitor::EtwMonitor(
_In_ const std::vector<ETWProvider>& Providers,
_In_ bool EventFormatMultiLine
_In_ std::wstring LogFormat,
_In_ std::wstring CustomLogFormat = L""
) :
m_eventFormatMultiLine(EventFormatMultiLine)
m_logFormat(LogFormat),
m_customLogFormat(CustomLogFormat)
{
//
// This is set as 'true' to stop processing events.
@ -721,11 +723,12 @@ EtwMonitor::OnRecordEvent(
///
/// Format ETW eventlog into a JSON output
///
std::wstring etwJsonFormat(EtwLogEntry* pLogEntry)
std::wstring EtwJsonFormat(EtwLogEntry* pLogEntry)
{
std::wostringstream oss;
// construct the JSON output
oss << L"{\"Source\":\"ETW\",\"LogEntry\":{";
oss << L"{\"Source\":\"" << pLogEntry->source << L"\",\"LogEntry\":{";
oss << L"\"Time\":\"" << pLogEntry->Time << L"\",";
oss << L"\"ProviderName\":\"" << pLogEntry->ProviderName << L"\",";
oss << L"\"ProviderId\":\"" << pLogEntry->ProviderId << "\",";
@ -737,8 +740,8 @@ std::wstring etwJsonFormat(EtwLogEntry* pLogEntry)
oss << L"\"Level\":\"" << pLogEntry->Level << L"\",";
oss << L"\"Keyword\":\"" << pLogEntry->Keyword << L"\",";
oss << L"\"EventId\":\"" << pLogEntry->EventId << "\",";
oss << L"\"EventData\":{";
bool firstEntry = true;
for (auto evtData : pLogEntry->EventData) {
oss << (firstEntry ? "" : ",");
@ -748,39 +751,52 @@ std::wstring etwJsonFormat(EtwLogEntry* pLogEntry)
Utility::SanitizeJson(key);
oss << "\"" << key << "\":";
//
// format (JSON) numbers without quotation marks, e.g.
/*
"EventData": {
"FrameUniqueID": 403787,
"PortNumber" : 0,
"TID" : 0,
"PeerID" : 0,
"PayloadLength" : 68,
"QueueLength" : 0,
"QueueState" : "false",
"CustomData1" : 24,
"CustomData2" : 0,
"CustomData3" : 0
}
*/
//
if (Utility::isJsonNumber(evtData.second)) {
oss << evtData.second;
}
else {
} else {
wstring value = evtData.second;
Utility::SanitizeJson(value);
oss << L"\"" << value << L"\"";
}
}
oss << L"}";
oss << L"}";
oss << L"},\"SchemaVersion\":\"1.0.0\"}";
return oss.str();
}
///
/// Format ETW eventlog into a XML output
///
std::wstring EtwXMLFormat(EtwLogEntry* pLogEntry)
{
std::wostringstream oss;
// construct the XML output
oss << L"<Log><Source>ETW</Source><LogEntry>";
oss << L"<Time>" << pLogEntry->Time << L"</Time>";
oss << L"<ProviderName>" << pLogEntry->ProviderName << L"</ProviderName>";
oss << L"<ProviderId>" << pLogEntry->ProviderId << "</ProviderId>";
oss << L"<DecodingSource>" << pLogEntry->DecodingSource << L"</DecodingSource>";
oss << L"<Execution>";
oss << L"<ProcessId>" << pLogEntry->ExecProcessId << L"</ProcessId>";
oss << L"<ThreadId>" << pLogEntry->ExecThreadId << L"</ThreadId>";
oss << "</Execution>";
oss << L"<Level>" << pLogEntry->Level << L"</Level>";
oss << L"<Keyword>" << pLogEntry->Keyword << L"</Keyword>";
oss << L"<EventId>" << pLogEntry->EventId << L"</EventId>";
oss << L"<EventData>";
for (auto evtData : pLogEntry->EventData) {
wstring key = evtData.first;
wstring value = evtData.second;
oss << "<" << key << ">" << value <<"</" << key << ">";
}
oss << L"</EventData></LogEntry></Log>";
return oss.str();
}
///
/// Prints the data and metadata of the event.
@ -826,7 +842,15 @@ EtwMonitor::PrintEvent(
return status;
}
std::wstring formattedEvent = etwJsonFormat(pLogEntry);
std::wstring formattedEvent;
if (Utility::CompareWStrings(m_logFormat, L"XML")) {
formattedEvent = EtwXMLFormat(pLogEntry);
} else if (Utility::CompareWStrings(m_logFormat, L"Custom")) {
formattedEvent = Utility::FormatEventLineLog(m_customLogFormat, pLogEntry, pLogEntry->source);
} else {
formattedEvent = EtwJsonFormat(pLogEntry);
}
logWriter.WriteConsoleLog(formattedEvent);
}
catch(std::bad_alloc&)
@ -860,6 +884,7 @@ EtwMonitor::FormatMetadata(
fileTime.dwHighDateTime = EventRecord->EventHeader.TimeStamp.HighPart;
fileTime.dwLowDateTime = EventRecord->EventHeader.TimeStamp.LowPart;
pLogEntry->source = L"ETW";
pLogEntry->Time = Utility::FileTimeToString(fileTime).c_str();
//
@ -1248,7 +1273,8 @@ EtwMonitor::GetPropertyLength(
// EVENT_PROPERTY_INFO.length field will be zero.
//
if (TDH_INTYPE_BINARY == EventInfo->EventPropertyInfoArray[Index].nonStructType.InType &&
TDH_OUTTYPE_IPV6 == EventInfo->EventPropertyInfoArray[Index].nonStructType.OutType)
TDH_OUTTYPE_IPV6 == EventInfo->EventPropertyInfoArray[Index].nonStructType.OutType &&
EventInfo->EventPropertyInfoArray[Index].length == 0)
{
PropertyLength = (USHORT)sizeof(IN6_ADDR);
}
@ -1258,6 +1284,39 @@ EtwMonitor::GetPropertyLength(
{
PropertyLength = EventInfo->EventPropertyInfoArray[Index].length;
}
else if (0 == (EventInfo->EventPropertyInfoArray[Index].Flags & (PropertyStruct | PropertyParamCount)) &&
EventInfo->EventPropertyInfoArray[Index].count == 1 )
{
BYTE const* pbData = static_cast<BYTE const*>(EventRecord->UserData);
BYTE const* pbDataEnd = pbData + EventRecord->UserDataLength;
switch (EventInfo->EventPropertyInfoArray[Index].nonStructType.InType)
{
case TDH_INTYPE_INT8:
case TDH_INTYPE_UINT8:
if ((pbDataEnd - pbData) >= 1)
{
PropertyLength = *pbData;
}
break;
case TDH_INTYPE_INT16:
case TDH_INTYPE_UINT16:
if ((pbDataEnd - pbData) >= 2)
{
PropertyLength = *reinterpret_cast<UINT16 const UNALIGNED*>(pbData);
}
break;
case TDH_INTYPE_INT32:
case TDH_INTYPE_UINT32:
case TDH_INTYPE_HEXINT32:
if ((pbDataEnd - pbData) >= 4)
{
auto val = *reinterpret_cast<UINT32 const UNALIGNED*>(pbData);
PropertyLength = static_cast<USHORT>(val > 0xffffu ? 0xffffu : val);
}
break;
}
}
else
{
logWriter.TraceError(
@ -1409,3 +1468,29 @@ EtwMonitor::RemoveTrailingSpace(
*((LPWSTR)((PBYTE)MapName + (MapName->MapEntryArray[i].OutputOffset + byteLength))) = L'\0';
}
}
std::wstring EtwMonitor::EtwFieldsMapping(_In_ std::wstring etwFields, _In_ void* pLogEntryData)
{
std::wostringstream oss;
EtwLogEntry* pLogEntry = (EtwLogEntry*)pLogEntryData;
if (Utility::CompareWStrings(etwFields, L"TimeStamp")) oss << pLogEntry->Time;
if (Utility::CompareWStrings(etwFields, L"Severity")) oss << pLogEntry->Level;
if (Utility::CompareWStrings(etwFields, L"Source")) oss << pLogEntry->source;
if (Utility::CompareWStrings(etwFields, L"ProviderId")) oss << pLogEntry->ProviderId;
if (Utility::CompareWStrings(etwFields, L"ProviderName")) oss << pLogEntry->ProviderName;
if (Utility::CompareWStrings(etwFields, L"DecodingSource")) oss << pLogEntry->DecodingSource;
if (Utility::CompareWStrings(etwFields, L"ExecutionProcessId")) oss << pLogEntry->ExecProcessId;
if (Utility::CompareWStrings(etwFields, L"ExecutionThreadId")) oss << pLogEntry->ExecThreadId;
if (Utility::CompareWStrings(etwFields, L"Keyword")) oss << pLogEntry->Keyword;
if (Utility::CompareWStrings(etwFields, L"EventId")) oss << pLogEntry->EventId;
if (Utility::CompareWStrings(etwFields, L"EventData")) {
for (auto evtData : pLogEntry->EventData) {
wstring key = evtData.first;
wstring value = evtData.second;
oss << key << ": " << value << " ";
}
}
return oss.str();
}

Просмотреть файл

@ -14,6 +14,7 @@ typedef LPTSTR(NTAPI* PIPV6ADDRTOSTRING)(
// struct to hold the ETW logEntry data
//
struct EtwLogEntry {
std::wstring source;
std::wstring Time;
std::wstring ProviderId;
std::wstring ProviderName;
@ -34,16 +35,20 @@ public:
EtwMonitor(
_In_ const std::vector<ETWProvider>& Providers,
_In_ bool EventFormatMultiLine
_In_ std::wstring LogFormat,
_In_ std::wstring CustomLogFormat
);
~EtwMonitor();
static std::wstring EtwFieldsMapping(_In_ std::wstring etwFields, _In_ void* pLogEntryData);
private:
static constexpr int ETW_MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS = 5 * 1000;
std::vector<ETWProvider> m_providersConfig;
bool m_eventFormatMultiLine;
std::wstring m_logFormat;
std::wstring m_customLogFormat;
TRACEHANDLE m_startTraceHandle;
//

Просмотреть файл

@ -27,11 +27,15 @@ using namespace std;
EventMonitor::EventMonitor(
_In_ const std::vector<EventLogChannel>& EventChannels,
_In_ bool EventFormatMultiLine,
_In_ bool StartAtOldestRecord
_In_ bool StartAtOldestRecord,
_In_ std::wstring LogFormat,
_In_ std::wstring CustomLogFormat = L""
) :
m_eventChannels(EventChannels),
m_eventFormatMultiLine(EventFormatMultiLine),
m_startAtOldestRecord(StartAtOldestRecord)
m_startAtOldestRecord(StartAtOldestRecord),
m_logFormat(LogFormat),
m_customLogFormat(CustomLogFormat)
{
m_stopEvent = NULL;
m_eventMonitorThread = NULL;
@ -411,6 +415,10 @@ EventMonitor::PrintEvent(
EVT_HANDLE renderContext = NULL;
EVT_HANDLE publisher = NULL;
// struct to hold the Event log entry and later format print
EventLogEntry logEntry;
EventLogEntry* pLogEntry = &logEntry;
static constexpr LPCWSTR defaultValuePaths[] = {
L"Event/System/Provider/@Name",
L"Event/System/Channel",
@ -499,7 +507,7 @@ EventMonitor::PrintEvent(
//
std::wstring providerName = (EvtVarTypeString != variants[0].Type) ? L"" : variants[0].StringVal;
std::wstring channelName = (EvtVarTypeString != variants[1].Type) ? L"" : variants[1].StringVal;
UINT16 eventId = (EvtVarTypeUInt16 != variants[2].Type) ? 0 : variants[2].UInt16Val;
pLogEntry->eventId = (EvtVarTypeUInt16 != variants[2].Type) ? 0 : variants[2].UInt16Val;
UINT8 level = (EvtVarTypeByte != variants[3].Type) ? 0 : variants[3].ByteVal;
ULARGE_INTEGER fileTimeAsInt{};
fileTimeAsInt.QuadPart = (EvtVarTypeFileTime != variants[4].Type) ? 0 : variants[4].FileTimeVal;
@ -552,20 +560,48 @@ EventMonitor::PrintEvent(
if (status == ERROR_SUCCESS)
{
// supporting JSON fmt by default
auto logFmt = L"{\"Source\": \"EventLog\",\"LogEntry\": {\"Time\": \"%s\",\"Channel\": \"%s\",\"Level\": \"%s\",\"EventId\": %u,\"Message\": \"%s\"}}";;
pLogEntry->source = L"EventLog";
pLogEntry->eventTime = Utility::FileTimeToString(fileTimeCreated);
pLogEntry->eventChannel = channelName;
pLogEntry->eventLevel = c_LevelToString[static_cast<UINT8>(level)];
pLogEntry->eventMessage = (LPWSTR)(&m_eventMessageBuffer[0]);
// sanitize message
std::wstring msg(m_eventMessageBuffer.begin(), m_eventMessageBuffer.end());
Utility::SanitizeJson(msg);
std::wstring formattedEvent;
if (Utility::CompareWStrings(m_logFormat, L"Custom")) {
formattedEvent = Utility::FormatEventLineLog(m_customLogFormat, pLogEntry, pLogEntry->source);
} else {
std::wstring logFmt;
if (Utility::CompareWStrings(m_logFormat, L"XML")) {
logFmt = L"<Log><Source>%s</Source><LogEntry><Time>%s</Time>"
L"<Channel>%s</Channel><Level>%s</Level>"
L"<EventId>%u</EventId><Message>%s</Message>"
L"</LogEntry></Log>";
} else {
logFmt = L"{\"Source\": \"%s\","
L"\"LogEntry\": {"
L"\"Time\": \"%s\","
L"\"Channel\": \"%s\","
L"\"Level\": \"%s\","
L"\"EventId\": %u,"
L"\"Message\": \"%s\""
L"}}";
std::wstring formattedEvent = Utility::FormatString(
logFmt,
Utility::FileTimeToString(fileTimeCreated).c_str(),
channelName.c_str(),
c_LevelToString[static_cast<UINT8>(level)].c_str(),
eventId,
msg.c_str());
// sanitize message
std::wstring msg(m_eventMessageBuffer.begin(), m_eventMessageBuffer.end());
Utility::SanitizeJson(msg);
pLogEntry->eventMessage = msg;
}
formattedEvent = Utility::FormatString(
logFmt.c_str(),
pLogEntry->source.c_str(),
pLogEntry->eventTime.c_str(),
pLogEntry->eventChannel.c_str(),
pLogEntry->eventLevel.c_str(),
pLogEntry->eventId,
pLogEntry->eventMessage.c_str()
);
}
logWriter.WriteConsoleLog(formattedEvent);
}
@ -784,3 +820,17 @@ Exit:
return status;
}
std::wstring EventMonitor::EventFieldsMapping(_In_ std::wstring eventField, _In_ void* pLogEntryData)
{
std::wostringstream oss;
EventLogEntry* pLogEntry = (EventLogEntry*)pLogEntryData;
if (Utility::CompareWStrings(eventField, L"TimeStamp")) oss << pLogEntry->eventTime;
if (Utility::CompareWStrings(eventField, L"Severity")) oss << pLogEntry->eventLevel;
if (Utility::CompareWStrings(eventField, L"Source")) oss << pLogEntry->source;
if (Utility::CompareWStrings(eventField, L"EventID")) oss << pLogEntry->eventId;
if (Utility::CompareWStrings(eventField, L"Message")) oss << pLogEntry->eventMessage;
return oss.str();
}

Просмотреть файл

@ -14,11 +14,15 @@ public:
EventMonitor(
_In_ const std::vector<EventLogChannel>& eventChannels,
_In_ bool EventFormatMultiLine,
_In_ bool StartAtOldestRecord
_In_ bool StartAtOldestRecord,
_In_ std::wstring LogFormat,
_In_ std::wstring CustomLogFormat
);
~EventMonitor();
static std::wstring EventFieldsMapping(_In_ std::wstring eventField, _In_ void* pLogEntryData);
private:
static constexpr int EVENT_MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS = 5 * 1000;
static constexpr int EVENT_ARRAY_SIZE = 10;
@ -26,6 +30,17 @@ private:
const std::vector<EventLogChannel> m_eventChannels;
bool m_eventFormatMultiLine;
bool m_startAtOldestRecord;
std::wstring m_logFormat;
std::wstring m_customLogFormat;
struct EventLogEntry {
std::wstring source;
std::wstring eventTime;
std::wstring eventChannel;
std::wstring eventLevel;
UINT16 eventId;
std::wstring eventMessage;
};
//
// Signaled by destructor to request the spawned thread to stop.

Просмотреть файл

@ -42,12 +42,16 @@ using namespace std;
LogFileMonitor::LogFileMonitor(_In_ const std::wstring& LogDirectory,
_In_ const std::wstring& Filter,
_In_ bool IncludeSubfolders,
_In_ const std::double_t& WaitInSeconds
_In_ const std::double_t& WaitInSeconds,
_In_ std::wstring LogFormat,
_In_ std::wstring CustomLogFormat = L""
) :
m_logDirectory(LogDirectory),
m_filter(Filter),
m_includeSubfolders(IncludeSubfolders),
m_waitInSeconds(WaitInSeconds)
m_waitInSeconds(WaitInSeconds),
m_logFormat(LogFormat),
m_customLogFormat(CustomLogFormat)
{
m_stopEvent = NULL;
m_overlappedEvent = NULL;
@ -1680,11 +1684,20 @@ LogFileMonitor::ReadLogFile(
}
void LogFileMonitor::WriteToConsole( _In_ std::wstring Message, _In_ std::wstring FileName) {
auto logFmt = L"{\"Source\":\"File\",\"LogEntry\":{\"Logline\":\"%s\",\"FileName\":\"%s\"},\"SchemaVersion\":\"1.0.0\"}";
size_t start = 0;
size_t i = 0;
wstring msg;
// struct to hold the File log entry and later format print
FileLogEntry logEntry;
FileLogEntry* pLogEntry = &logEntry;
SYSTEMTIME st;
GetSystemTime(&st);
pLogEntry->source = L"File";
pLogEntry->currentTime = Utility::SystemTimeToString(st).c_str();
while (true) {
i = Message.find(L"\n", start);
if (i == std::string::npos) {
@ -1704,12 +1717,43 @@ void LogFileMonitor::WriteToConsole( _In_ std::wstring Message, _In_ std::wstrin
if (msg.size() > 0) {
// escape backslashes in FileName
auto fmtFileName = Utility::ReplaceAll(FileName, L"\\", L"\\\\");
// sanitize msg
Utility::SanitizeJson(msg);
auto log = Utility::FormatString(logFmt, msg.c_str(), fmtFileName.c_str());
logWriter.WriteConsoleLog(log);
}
pLogEntry->fileName = fmtFileName;
pLogEntry->message = msg;
std::wstring formattedFileEntry;
if (Utility::CompareWStrings(m_logFormat, L"Custom")) {
formattedFileEntry = Utility::FormatEventLineLog(m_customLogFormat, pLogEntry, pLogEntry->source);
} else {
std::wstring logFmt;
if (Utility::CompareWStrings(m_logFormat, L"XML")) {
logFmt = L"<Log><Source>File</Source>"
L"<LogEntry>"
L"<Logline>%s</Logline>"
L"<FileName>%s</FileName>"
L"</LogEntry>"
L"</Log>";
} else {
logFmt = L"{\"Source\": \"File\","
L"\"LogEntry\": {"
L"\"Logline\": \"%s\","
L"\"FileName\": \"%s\""
L"},"
L"\"SchemaVersion\":\"1.0.0\""
L"}";
// sanitize message
Utility::SanitizeJson(msg);
pLogEntry->message = msg;
}
formattedFileEntry = Utility::FormatString(
logFmt.c_str(),
pLogEntry->message.c_str(),
pLogEntry->fileName.c_str()
);
}
logWriter.WriteConsoleLog(formattedFileEntry);
}
if (i >= Message.size()) break;
}
}
@ -2045,3 +2089,16 @@ LogFileMonitor::GetFileId(
return status;
}
std::wstring LogFileMonitor::FileFieldsMapping(_In_ std::wstring fileFields, _In_ void* pLogEntryData)
{
std::wostringstream oss;
FileLogEntry* pLogEntry = (FileLogEntry*)pLogEntryData;
if (Utility::CompareWStrings(fileFields, L"TimeStamp")) oss << pLogEntry->currentTime;
if (Utility::CompareWStrings(fileFields, L"FileName")) oss << pLogEntry->fileName;
if (Utility::CompareWStrings(fileFields, L"Source")) oss << pLogEntry->source;
if (Utility::CompareWStrings(fileFields, L"Message")) oss << pLogEntry->message;
return oss.str();
}

Просмотреть файл

@ -63,10 +63,14 @@ public:
_In_ const std::wstring &LogDirectory,
_In_ const std::wstring &Filter,
_In_ bool IncludeSubfolders,
_In_ const std::double_t &WaitInSeconds);
_In_ const std::double_t &WaitInSeconds,
_In_ std::wstring LogFormat,
_In_ std::wstring CustomLogFormat);
~LogFileMonitor();
static std::wstring FileFieldsMapping(_In_ std::wstring eventFields, _In_ void* pLogEntryData);
private:
static constexpr int LOG_MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS = 5 * 1000;
static constexpr int RECORDS_BUFFER_SIZE_BYTES = 8 * 1024;
@ -76,6 +80,15 @@ private:
std::wstring m_filter;
std::double_t m_waitInSeconds;
bool m_includeSubfolders;
std::wstring m_logFormat;
std::wstring m_customLogFormat;
struct FileLogEntry {
std::wstring source;
std::wstring currentTime;
std::wstring fileName;
std::wstring message;
};
//
// Signaled by destructor to request the spawned thread to stop.

Просмотреть файл

@ -24,6 +24,7 @@ HANDLE g_hStopEvent = INVALID_HANDLE_VALUE;
std::unique_ptr<EventMonitor> g_eventMon(nullptr);
std::vector<std::shared_ptr<LogFileMonitor>> g_logfileMonitors;
std::unique_ptr<EtwMonitor> g_etwMon(nullptr);
std::wstring logFormat, processMonitorCustomFormat;
/// Handle signals.
///
@ -104,6 +105,10 @@ void StartMonitors(_In_ LoggerSettings& settings)
bool eventMonMultiLine;
bool eventMonStartAtOldestRecord;
bool etwMonMultiLine;
logFormat = settings.LogFormat;
std::wstring eventCustomLogFormat;
std::wstring etwCustomLogFormat;
std::wstring processCustomLogFormat;
for (auto source : settings.Sources)
{
@ -121,6 +126,7 @@ void StartMonitors(_In_ LoggerSettings& settings)
eventMonMultiLine = sourceEventLog->EventFormatMultiLine;
eventMonStartAtOldestRecord = sourceEventLog->StartAtOldestRecord;
eventCustomLogFormat = sourceEventLog->CustomLogFormat;
break;
}
@ -134,7 +140,9 @@ void StartMonitors(_In_ LoggerSettings& settings)
sourceFile->Directory,
sourceFile->Filter,
sourceFile->IncludeSubdirectories,
sourceFile->WaitInSeconds
sourceFile->WaitInSeconds,
logFormat,
sourceFile->CustomLogFormat
);
g_logfileMonitors.push_back(std::move(logfileMon));
}
@ -170,17 +178,43 @@ void StartMonitors(_In_ LoggerSettings& settings)
}
etwMonMultiLine = sourceETW->EventFormatMultiLine;
etwCustomLogFormat = sourceETW->CustomLogFormat;
break;
}
} // Switch
case LogSourceType::Process:
{
std::shared_ptr<SourceProcess> sourceProcess = std::reinterpret_pointer_cast<SourceProcess>(source);
try
{
processMonitorCustomFormat = sourceProcess->CustomLogFormat;
}
catch (std::exception& ex)
{
logWriter.TraceError(
Utility::FormatString(
L"Instantiation of a ProcessMonitor object failed. %S", ex.what()
).c_str()
);
}
break;
}
}// Switch
}
if (!eventChannels.empty())
{
try
{
g_eventMon = make_unique<EventMonitor>(eventChannels, eventMonMultiLine, eventMonStartAtOldestRecord);
g_eventMon = make_unique<EventMonitor>(
eventChannels,
eventMonMultiLine,
eventMonStartAtOldestRecord,
logFormat,
eventCustomLogFormat
);
}
catch (std::exception& ex)
{
@ -205,7 +239,7 @@ void StartMonitors(_In_ LoggerSettings& settings)
{
try
{
g_etwMon = make_unique<EtwMonitor>(etwProviders, etwMonMultiLine);
g_etwMon = make_unique<EtwMonitor>(etwProviders, logFormat, etwCustomLogFormat);
}
catch (...)
{
@ -296,7 +330,7 @@ int __cdecl wmain(int argc, WCHAR *argv[])
cmdline += argv[i];
}
exitcode = CreateAndMonitorProcess(cmdline);
exitcode = CreateAndMonitorProcess(cmdline, logFormat, processMonitorCustomFormat);
}
else
{

Просмотреть файл

@ -10,6 +10,12 @@
#define JSON_TAG_LOG_CONFIG L"LogConfig"
#define JSON_TAG_SOURCES L"sources"
///
/// Log formatting attributes
///
#define JSON_TAG_LOG_FORMAT L"logFormat"
#define JSON_TAG_CUSTOM_LOG_FORMAT L"customLogFormat"
///
/// Valid source attributes
///
@ -132,7 +138,8 @@ enum class LogSourceType
{
EventLog = 0,
File,
ETW
ETW,
Process
};
///
@ -141,7 +148,8 @@ enum class LogSourceType
const LPCWSTR LogSourceTypeNames[] = {
L"EventLog",
L"File",
L"ETW"
L"ETW",
L"Process"
};
///
@ -195,6 +203,7 @@ public:
std::vector<EventLogChannel> Channels;
bool EventFormatMultiLine = true;
bool StartAtOldestRecord = false;
std::wstring CustomLogFormat = L"[%TimeStamp%] [%Source%] [%Severity%] %Message%";
static bool Unwrap(
_In_ AttributesMap& Attributes,
@ -234,6 +243,15 @@ public:
NewSource.StartAtOldestRecord = *(bool*)Attributes[JSON_TAG_START_AT_OLDEST_RECORD];
}
//
// lineLogFormat is an optional value
//
if (Attributes.find(JSON_TAG_CUSTOM_LOG_FORMAT) != Attributes.end()
&& Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] != nullptr)
{
NewSource.CustomLogFormat = *(std::wstring*)Attributes[JSON_TAG_CUSTOM_LOG_FORMAT];
}
return true;
}
};
@ -247,6 +265,7 @@ public:
std::wstring Directory;
std::wstring Filter;
bool IncludeSubdirectories = false;
std::wstring CustomLogFormat = L"[%TimeStamp%] [%Source%] [%FileName%] %Message%";
// Default wait time: 5minutes
std::double_t WaitInSeconds = 300;
@ -298,6 +317,15 @@ public:
NewSource.WaitInSeconds = *(std::double_t*)Attributes[JSON_TAG_WAITINSECONDS];
}
//
// lineLogFormat is an optional value
//
if (Attributes.find(JSON_TAG_CUSTOM_LOG_FORMAT) != Attributes.end()
&& Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] != nullptr)
{
NewSource.CustomLogFormat = *(std::wstring*)Attributes[JSON_TAG_CUSTOM_LOG_FORMAT];
}
return true;
}
};
@ -364,6 +392,9 @@ class SourceETW : LogSource
public:
std::vector<ETWProvider> Providers;
bool EventFormatMultiLine = true;
std::wstring CustomLogFormat = L"[%TimeStamp%] [%Source%] [%Severity%] "
L"[%ProviderId%] [%ProviderName%] "
L"[%EventId%] %EventData%";
static bool Unwrap(
_In_ AttributesMap& Attributes,
@ -394,15 +425,52 @@ public:
NewSource.EventFormatMultiLine = *(bool*)Attributes[JSON_TAG_FORMAT_MULTILINE];
}
//
// lineLogFormat is an optional value
//
if (Attributes.find(JSON_TAG_CUSTOM_LOG_FORMAT) != Attributes.end()
&& Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] != nullptr)
{
NewSource.CustomLogFormat = *(std::wstring*)Attributes[JSON_TAG_CUSTOM_LOG_FORMAT];
}
return true;
}
};
///
/// Represents a Source if Proccess type
///
class SourceProcess : LogSource
{
public:
std::wstring CustomLogFormat = L"[%TimeStamp%] [%Source%] [%LogEntry%]";
static bool Unwrap(
_In_ AttributesMap& Attributes,
_Out_ SourceProcess& NewSource)
{
NewSource.Type = LogSourceType::Process;
//
// lineLogFormat is an optional value
//
if (Attributes.find(JSON_TAG_CUSTOM_LOG_FORMAT) != Attributes.end()
&& Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] != nullptr)
{
NewSource.CustomLogFormat = *(std::wstring*)Attributes[JSON_TAG_CUSTOM_LOG_FORMAT];
}
return true;
}
};
///
/// Information about a channel Log
///
typedef struct _LoggerSettings
{
std::vector<std::shared_ptr<LogSource> > Sources;
std::wstring LogFormat = L"JSON";
} LoggerSettings;

Просмотреть файл

@ -15,9 +15,10 @@ HANDLE g_hChildStd_OUT_Wr = NULL;
DWORD g_processId = 0;
wstring g_processName = L"";
wstring loggingformat, processCustomLogFormat;
DWORD CreateChildProcess(std::wstring& Cmdline);
DWORD ReadFromPipe(LPVOID Param);
ProcessMonitor::ProcessMonitor(){}
///
/// Creates a new process, and link its STDIN and STDOUT to the LogMonitor proccess' ones.
@ -26,8 +27,11 @@ DWORD ReadFromPipe(LPVOID Param);
///
/// \return Status
///
DWORD CreateAndMonitorProcess(std::wstring& Cmdline)
DWORD CreateAndMonitorProcess(std::wstring& Cmdline, std::wstring LogFormat, std::wstring ProcessCustomLogFormat)
{
loggingformat = LogFormat;
processCustomLogFormat = ProcessCustomLogFormat;
SECURITY_ATTRIBUTES saAttr;
DWORD status = ERROR_SUCCESS;
@ -174,7 +178,7 @@ DWORD CreateChildProcess(std::wstring& Cmdline)
/// Helper function for making a copy of the buffer.
/// returns the index after the last copied byte.
///
size_t bufferCopy(char* dst, char* src, size_t start, size_t end = 0)
size_t BufferCopy(char* dst, char* src, size_t start, size_t end = 0)
{
char* ptr = src;
size_t i = start;
@ -195,7 +199,7 @@ size_t bufferCopy(char* dst, char* src, size_t start, size_t end = 0)
/// For optimization, the function also "sanitizes" the string for JSON.
/// returns the index after the last copied byte.
///
size_t bufferCopyAndSanitize(char* dst, char* src)
size_t BufferCopyAndSanitize(char* dst, char* src)
{
char* ptr = src;
size_t i = 0;
@ -229,47 +233,93 @@ size_t bufferCopyAndSanitize(char* dst, char* src)
}
///
/// Helper function to formats the stdout buffer to include the other
/// Helper function to format the stdout buffer to include additional
/// details from the JSON schema.
/// Returns the number of bytes written to the buffer.
///
size_t formatProcessLog(char* chBuf)
{
// {"Source":"Process","LogEntry":{"Logline":"<chBuf>"},"SchemaVersion":"1.0.0"}
const char* prefix = "{\"Source\":\"Process\",\"LogEntry\":{\"Logline\":\"";
const char* suffix = "\"},\"SchemaVersion\":\"1.0.0\"}\n";
size_t FormatProcessLog(char* chBuf) {
if (Utility::CompareWStrings(loggingformat, L"Custom")) {
return FormatCustomLog(chBuf);
} else {
return FormatStandardLog(chBuf);
}
}
///
/// Helper function to format the custom log.
///
size_t FormatCustomLog(char* chBuf) {
ProcessLogEntry logEntry;
SYSTEMTIME st;
GetSystemTime(&st);
char chBufCpy[BUFSIZE] = "";
size_t chBufLen = BufferCopyAndSanitize(chBufCpy, chBuf);
logEntry.source = L"Process";
logEntry.currentTime = Utility::SystemTimeToString(st).c_str();
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> fromBytesconverter;
logEntry.logLine = fromBytesconverter.from_bytes(chBufCpy);
std::wstring_convert<std::codecvt_utf8<wchar_t>> toBytesconverter;
std::wstring formattedLog = Utility::FormatEventLineLog(processCustomLogFormat, &logEntry, logEntry.source);
std::string convertedLog = toBytesconverter.to_bytes(formattedLog);
std::string str = convertedLog + "\n";
const char* logLine = str.c_str();
size_t logLineLen = strlen(logLine);
return BufferCopy(chBuf, const_cast<char*>(logLine), 0, logLineLen);
}
///
/// Helper function to format the standard log (JSON or XML).
///
size_t FormatStandardLog(char* chBuf) {
const char* prefix;
const char* suffix;
if (Utility::CompareWStrings(loggingformat, L"XML")) {
prefix = "<Log><Source>Process</Source><LogEntry><Logline>";
suffix = "</Logline></LogEntry></Log>\n";
} else {
prefix = "{\"Source\":\"Process\",\"LogEntry\":{\"Logline\":\"";
suffix = "\"},\"SchemaVersion\":\"1.0.0\"}\n";
}
char chBufCpy[BUFSIZE] = "";
//
// copy valid (>0 ASCII values) bytes from chBuf to chBufCpy
//
size_t chBufLen = bufferCopyAndSanitize(chBufCpy, chBuf);
size_t chBufLen = BufferCopyAndSanitize(chBufCpy, chBuf);
size_t prefixLen = strlen(prefix);
size_t suffixLen = strlen(suffix);
size_t index = bufferCopy(chBuf, const_cast<char*>(prefix), 0, prefixLen);
size_t index = BufferCopy(chBuf, const_cast<char*>(prefix), 0, prefixLen);
// copy over the logline after prefix
// index increments from the previous index within bufferCopy
index = bufferCopy(chBuf, chBufCpy, index);
index = BufferCopy(chBuf, chBufCpy, index);
// truncate, in the unlikely event of a long logline > |BUFSIZE-85|
// leave at least 36 slots to close the JSON with `..."},\"SchemaVersion\":\"1.0.0\"}\n`
// leave at least 36 slots for JSON or 21 slots for XML
// reset the start index
if ((index + suffixLen) > BUFSIZE - 5) {
index = BUFSIZE - 5 - suffixLen;
suffix = "...\"},\"SchemaVersion\":\"1.0.0\"}\n";
if (Utility::CompareWStrings(loggingformat, L"XML"))
{
suffix = "...\</Logline></LogEntry></Log>\n";
} else {
suffix = "...\"},\"SchemaVersion\":\"1.0.0\"}\n";
}
}
index = bufferCopy(chBuf, const_cast<char*>(suffix), index, index + suffixLen);
return index; // same as the number of bytes read
return BufferCopy(chBuf, const_cast<char*>(suffix), index, index + suffixLen);
}
///
/// Helper function to clear the stdout buffer
/// return number of bytes cleared
///
size_t clearBuffer(char* chBuf) {
size_t ClearBuffer(char* chBuf) {
size_t count = 0;
char* ptr = chBuf;
@ -303,7 +353,7 @@ DWORD ReadFromPipe(LPVOID Param)
for (;;)
{
// clear buffer ready for read
clearBuffer(chBuf);
ClearBuffer(chBuf);
// move valid chars from remainder buffer to chBuf
// then ReadFile starts from the end position (chBuf + cnt)
char* ptrRem = chBufRem;
@ -315,7 +365,7 @@ DWORD ReadFromPipe(LPVOID Param)
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf + cnt, BUFSIZE - cnt, &dwRead, NULL);
// clear remainder buffer for the next read
clearBuffer(chBufRem);
ClearBuffer(chBufRem);
if (!bSuccess || dwRead == 0)
{
@ -327,7 +377,7 @@ DWORD ReadFromPipe(LPVOID Param)
size_t outSz = 0;
size_t count = 0;
size_t lastNewline = 0;
clearBuffer(chBufOut);
ClearBuffer(chBufOut);
while (*ptr != '\0' && count < BUFSIZE) {
// copy over to chBufOut till \r\n
// the remaining will be reserved to be completed
@ -335,7 +385,7 @@ DWORD ReadFromPipe(LPVOID Param)
if (*ptr == '\r' || *ptr == '\n') {
if (outSz > 0) {
// print out and reset chBufOut and outSz
size_t sz = formatProcessLog(chBufOut);
size_t sz = FormatProcessLog(chBufOut);
DWORD dwRead = static_cast<DWORD>(sz);
bSuccess = logWriter.WriteLog(
@ -346,7 +396,7 @@ DWORD ReadFromPipe(LPVOID Param)
NULL);
// reset
outSz = 0;
clearBuffer(chBufOut);
ClearBuffer(chBufOut);
lastNewline = count;
}
}
@ -374,3 +424,16 @@ DWORD ReadFromPipe(LPVOID Param)
return ERROR_SUCCESS;
}
std::wstring ProcessMonitor::ProcessFieldsMapping(_In_ std::wstring fileFields, _In_ void* pLogEntryData)
{
std::wostringstream oss;
ProcessLogEntry* pLogEntry = (ProcessLogEntry*)pLogEntryData;
if (Utility::CompareWStrings(fileFields, L"TimeStamp")) oss << pLogEntry->currentTime;
if (Utility::CompareWStrings(fileFields, L"Source")) oss << pLogEntry->source;
if (Utility::CompareWStrings(fileFields, L"logLine")
|| Utility::CompareWStrings(fileFields, L"logEntry")) oss << pLogEntry->logLine;
return oss.str();
}

Просмотреть файл

@ -5,5 +5,34 @@
#pragma once
DWORD CreateAndMonitorProcess(std::wstring& Cmdline);
struct ProcessLogEntry {
std::wstring source;
std::wstring currentTime;
std::wstring logLine;
};
DWORD CreateAndMonitorProcess(std::wstring& Cmdline, std::wstring LogFormat, std::wstring ProcessCustomLogFormat);
DWORD CreateChildProcess(std::wstring& Cmdline);
static DWORD ReadFromPipe(LPVOID Param);
static size_t ClearBuffer(char* chBuf);
size_t FormatProcessLog(char* chBuf);
size_t FormatCustomLog(char* chBuf);
size_t FormatStandardLog(char* chBuf);
static size_t BufferCopy(char* dst, char* src, size_t start, size_t end);
static size_t BufferCopyAndSanitize(char* dst, char* src);
class ProcessMonitor final
{
public:
ProcessMonitor();
static std::wstring ProcessFieldsMapping(_In_ std::wstring eventFields, _In_ void* pLogEntryData);
};

Просмотреть файл

@ -268,12 +268,16 @@ 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"\\")
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"\\") {
@ -343,3 +347,96 @@ int Utility::GetWaitInterval(_In_ std::double_t waitInSeconds, _In_ int elapsedT
const auto remainingTime = static_cast<int>(waitInSeconds - elapsedTime);
return remainingTime <= WAIT_INTERVAL ? remainingTime : WAIT_INTERVAL;
}
/// <summary>
/// Comparing wstrings with ignoring the case
/// </summary>
/// <param name="stringA"></param>
/// <param name="stringB"></param>
/// <returns></returns>
///
bool Utility::CompareWStrings(wstring stringA, wstring stringB)
{
return stringA.size() == stringB.size() &&
equal(
stringA.cbegin(),
stringA.cend(),
stringB.cbegin(),
[](wstring::value_type l1, wstring::value_type r1) {
return towupper(l1) == towupper(r1);
}
);
}
std::wstring Utility::FormatEventLineLog(
_In_ std::wstring customLogFormat,
_In_ void* pLogEntry,
_In_ std::wstring sourceType
)
{
bool customJsonFormat = IsCustomJsonFormat(customLogFormat);
size_t i = 0, j = 1;
while (i < customLogFormat.size()) {
auto sub = customLogFormat.substr(i, j - i);
auto sub_length = sub.size();
bool startsWithPercent = sub[0] == '%';
bool endsWithPercent = sub[sub_length - 1] == '%';
if (!startsWithPercent && !endsWithPercent) {
j++, i++;
} else if (startsWithPercent && endsWithPercent && sub_length > 1) {
// Valid field name found in custom log format
wstring fieldValue;
auto fieldName = sub.substr(1, sub_length - 2);
if (sourceType == L"ETW") {
fieldValue = EtwMonitor::EtwFieldsMapping(fieldName, pLogEntry);
} else if (sourceType == L"EventLog") {
fieldValue = EventMonitor::EventFieldsMapping(fieldName, pLogEntry);
} else if (sourceType == L"File") {
fieldValue = LogFileMonitor::FileFieldsMapping(fieldName, pLogEntry);
} else if (sourceType == L"Process") {
fieldValue = ProcessMonitor::ProcessFieldsMapping(fieldName, pLogEntry);
}
// Substitute the field name with value
customLogFormat.replace(i, sub_length, fieldValue);
i += fieldValue.length();
j = i + 1;
} else {
j++;
}
}
if(customJsonFormat)
SanitizeJson(customLogFormat);
return customLogFormat;
}
/// <summary>
/// check if custom format specified in config is JSON for sanitization purposes
/// </summary>
/// <param name="customLogFormat"></param>
/// <returns></returns>
bool Utility::IsCustomJsonFormat(_Inout_ std::wstring& customLogFormat)
{
bool isCustomJSONFormat = false;
auto npos = customLogFormat.find_last_of(L"|");
std::wstring substr;
if (npos != std::string::npos) {
substr = customLogFormat.substr(npos + 1);
substr.erase(std::remove(substr.begin(), substr.end(), ' '), substr.end());
if (!substr.empty() && CompareWStrings(substr, L"JSON")) {
customLogFormat = ReplaceAll(customLogFormat, L"'", L"~\"");
isCustomJSONFormat = true;
}
customLogFormat = customLogFormat.substr(0, customLogFormat.find_last_of(L"|"));
}
return isCustomJSONFormat;
}

Просмотреть файл

@ -77,4 +77,17 @@ public:
static int GetWaitInterval(
_In_ std::double_t waitInSeconds,
_In_ int elapsedTime);
static bool CompareWStrings(
_In_ std::wstring stringA,
_In_ std::wstring stringB
);
static std::wstring FormatEventLineLog(
_In_ std::wstring customLogFormat,
_In_ void* pLogEntry,
_In_ std::wstring sourceType
);
static bool IsCustomJsonFormat(_Inout_ std::wstring& customLogFormat);
};

Просмотреть файл

@ -1,5 +1,6 @@
{
"LogConfig": {
"logFormat": "json",
"sources": [
{
"type": "EventLog",

Просмотреть файл

@ -7,8 +7,8 @@
#define _VERSION_H_
#define LM_MAJORNUMBER 2
#define LM_MINORNUMBER 0
#define LM_PATCHNUMBER 2
#define LM_MINORNUMBER 1
#define LM_PATCHNUMBER 0
// removed in support of semantic versioning - https://semver.org
// major.minor.patch
// #define LM_BUILDMINORVERSION 0