This is an automated email from the ASF dual-hosted git repository.
szaszm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new bb8ea06 MINIFICPP-1448 CWEL JSON output
bb8ea06 is described below
commit bb8ea0632d50d9ac43e634847ff13c0955bc43ff
Author: Adam Debreceni <[email protected]>
AuthorDate: Sat Jan 23 21:42:30 2021 +0100
MINIFICPP-1448 CWEL JSON output
This closes #976
Signed-off-by: Marton Szasz <[email protected]>
Co-authored-by: Marton Szasz <[email protected]>
---
.github/workflows/ci.yml | 10 +-
.../windows-event-log/ConsumeWindowsEventLog.cpp | 132 +++++++++++-----
.../windows-event-log/ConsumeWindowsEventLog.h | 40 +++--
extensions/windows-event-log/tests/CMakeLists.txt | 10 +-
.../tests/CWELCustomProviderTests.cpp | 176 +++++++++++++++++++++
extensions/windows-event-log/tests/CWELTestUtils.h | 117 ++++++++++++++
.../tests/ConsumeWindowsEventLogTests.cpp | 127 ++++++++++-----
.../custom-provider/generate-and-register.bat | 62 ++++++++
extensions/windows-event-log/wel/JSONUtils.cpp | 170 ++++++++++++++++++++
extensions/windows-event-log/wel/JSONUtils.h | 86 ++++++++++
.../windows-event-log/wel/MetadataWalker.cpp | 5 +-
.../windows-event-log/wel/WindowsEventLog.cpp | 5 +
win_build_vs.bat | 5 +-
13 files changed, 846 insertions(+), 99 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1e00d42..69e8717 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -47,7 +47,10 @@ jobs:
- name: Setup PATH
uses: microsoft/[email protected]
- id: build
- run: win_build_vs.bat build /CI /S /A
+ run: |
+ PATH %PATH%;C:\Program Files (x86)\Windows
Kits\10\bin\10.0.17763.0\x86
+ PATH %PATH%;C:\Program Files (x86)\Microsoft Visual
Studio\2017\Enterprise\MSBuild\15.0\Bin\Roslyn
+ win_build_vs.bat build /CI /S /A
shell: cmd
windows_VS2019:
name: "windows-vs2019"
@@ -59,7 +62,10 @@ jobs:
- name: Setup PATH
uses: microsoft/[email protected]
- id: build
- run: win_build_vs.bat build /2019 /64 /CI
+ run: |
+ PATH %PATH%;C:\Program Files (x86)\Windows
Kits\10\bin\10.0.19041.0\x64
+ PATH %PATH%;C:\Program Files (x86)\Microsoft Visual
Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn
+ win_build_vs.bat build /2019 /64 /CI
shell: cmd
ubuntu_16_04:
name: "ubuntu-16.04"
diff --git a/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
b/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
index 22442f9..f262402 100644
--- a/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
+++ b/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
@@ -34,6 +34,7 @@
#include "wel/MetadataWalker.h"
#include "wel/XMLString.h"
#include "wel/UnicodeConversion.h"
+#include "wel/JSONUtils.h"
#include "io/BufferStream.h"
#include "core/ProcessContext.h"
@@ -134,8 +135,16 @@ core::Property ConsumeWindowsEventLog::OutputFormat(
core::PropertyBuilder::createProperty("Output Format")->
isRequired(true)->
withDefaultValue(Both)->
- withAllowableValues<std::string>({XML, Plaintext, Both})->
- withDescription("Set the output format type. In case \'Both\' is selected
the processor generates two flow files for every event captured")->
+ withAllowableValues<std::string>({XML, Plaintext, Both, JSON})->
+ withDescription("Set the output format type. In case \'Both\' is selected
the processor generates two flow files for every event captured in format XML
and Plaintext")->
+ build());
+
+core::Property ConsumeWindowsEventLog::JSONFormat(
+ core::PropertyBuilder::createProperty("JSON Format")->
+ isRequired(true)->
+ withDefaultValue(JSONSimple)->
+ withAllowableValues<std::string>({JSONSimple, JSONFlattened, JSONRaw})->
+ withDescription("Set the json format type. Only applicable if Output Format
is set to 'JSON'")->
build());
core::Property ConsumeWindowsEventLog::BatchCommitSize(
@@ -162,7 +171,8 @@ core::Property ConsumeWindowsEventLog::ProcessOldEvents(
core::Relationship ConsumeWindowsEventLog::Success("success", "Relationship
for successfully consumed events.");
ConsumeWindowsEventLog::ConsumeWindowsEventLog(const std::string& name,
utils::Identifier uuid)
- : core::Processor(name, uuid),
logger_(logging::LoggerFactory<ConsumeWindowsEventLog>::getLogger()),
apply_identifier_function_(false), batch_commit_size_(0U) {
+ : core::Processor(name, uuid),
+ logger_(logging::LoggerFactory<ConsumeWindowsEventLog>::getLogger()) {
char buff[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(buff);
if (GetComputerName(buff, &size)) {
@@ -170,9 +180,6 @@ ConsumeWindowsEventLog::ConsumeWindowsEventLog(const
std::string& name, utils::I
} else {
LogWindowsError();
}
-
- writeXML_ = false;
- writePlainText_ = false;
}
void ConsumeWindowsEventLog::notifyStop() {
@@ -199,7 +206,7 @@ void ConsumeWindowsEventLog::initialize() {
//! Set the supported properties
setSupportedProperties({
Channel, Query, MaxBufferSize, InactiveDurationToReconnect,
IdentifierMatcher, IdentifierFunction, ResolveAsAttributes,
- EventHeaderDelimiter, EventHeader, OutputFormat, BatchCommitSize,
BookmarkRootDirectory, ProcessOldEvents
+ EventHeaderDelimiter, EventHeader, OutputFormat, JSONFormat,
BatchCommitSize, BookmarkRootDirectory, ProcessOldEvents
});
//! Set the supported relationships
@@ -252,11 +259,31 @@ void ConsumeWindowsEventLog::onSchedule(const
std::shared_ptr<core::ProcessConte
std::string mode;
context->getProperty(OutputFormat.getName(), mode);
- writeXML_ = (mode == Both || mode == XML);
-
- writePlainText_ = (mode == Both || mode == Plaintext);
+ output_ = {};
+ if (mode == XML) {
+ output_.xml = true;
+ } else if (mode == Plaintext) {
+ output_.plaintext = true;
+ } else if (mode == Both) {
+ output_.xml = true;
+ output_.plaintext = true;
+ } else if (mode == JSON) {
+ std::string json_format;
+ context->getProperty(JSONFormat.getName(), json_format);
+ if (json_format == JSONRaw) {
+ output_.json.type = JSONType::Raw;
+ } else if (json_format == JSONSimple) {
+ output_.json.type = JSONType::Simple;
+ } else if (json_format == JSONFlattened) {
+ output_.json.type = JSONType::Flattened;
+ }
+ } else {
+ // in the future this might be considered an error, but for now due to
backwards
+ // compatibility we just fall through and execute the processor outputing
nothing
+ // throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Unrecognized output
format: " + mode);
+ }
- if (writeXML_ && !hMsobjsDll_) {
+ if ((output_.xml || output_.json) && !hMsobjsDll_) {
char systemDir[MAX_PATH];
if (GetSystemDirectory(systemDir, sizeof(systemDir))) {
hMsobjsDll_ = LoadLibrary((systemDir +
std::string("\\msobjs.dll")).c_str());
@@ -564,7 +591,7 @@ bool ConsumeWindowsEventLog::createEventRender(EVT_HANDLE
hEvent, EventRender& e
logger_->log_debug("Finish doc traversing, performing writing...");
- if (writePlainText_) {
+ if (output_.plaintext) {
logger_->log_trace("Writing event in plain text");
auto handler = getEventLogHandler(providerName);
@@ -583,30 +610,47 @@ bool ConsumeWindowsEventLog::createEventRender(EVT_HANDLE
hEvent, EventRender& e
// set the delimiter
log_header.setDelimiter(header_delimiter_);
// render the header.
- eventRender.rendered_text_ =
log_header.getEventHeader([&walker](wel::METADATA metadata) { return
walker.getMetadata(metadata); });
- eventRender.rendered_text_ += "Message" + header_delimiter_ + " ";
- eventRender.rendered_text_ += message;
+ eventRender.plaintext =
log_header.getEventHeader([&walker](wel::METADATA metadata) { return
walker.getMetadata(metadata); });
+ eventRender.plaintext += "Message" + header_delimiter_ + " ";
+ eventRender.plaintext += message;
}
logger_->log_trace("Finish writing in plain text");
}
- if (writeXML_) {
- logger_->log_trace("Writing event in XML");
+ if (output_.xml || output_.json) {
substituteXMLPercentageItems(doc);
logger_->log_trace("Finish substituting %% in XML");
if (resolve_as_attributes_) {
- eventRender.matched_fields_ = walker.getFieldValues();
+ eventRender.matched_fields = walker.getFieldValues();
}
+ }
+
+ if (output_.xml) {
+ logger_->log_trace("Writing event in XML");
wel::XmlString writer;
doc.print(writer, "", pugi::format_raw); // no indentation or formatting
xml = writer.xml_;
- eventRender.text_ = std::move(xml);
+ eventRender.xml = std::move(xml);
logger_->log_trace("Finish writing in XML");
}
+ if (output_.json.type == JSONType::Raw) {
+ logger_->log_trace("Writing event in raw JSON");
+ eventRender.json = wel::jsonToString(wel::toRawJSON(doc));
+ logger_->log_trace("Finish writing in raw JSON");
+ } else if (output_.json.type == JSONType::Simple) {
+ logger_->log_trace("Writing event in simple JSON");
+ eventRender.json = wel::jsonToString(wel::toSimpleJSON(doc));
+ logger_->log_trace("Finish writing in simple JSON");
+ } else if (output_.json.type == JSONType::Flattened) {
+ logger_->log_trace("Writing event in flattened JSON");
+ eventRender.json = wel::jsonToString(wel::toFlattenedJSON(doc));
+ logger_->log_trace("Finish writing in flattened JSON");
+ }
+
return true;
}
@@ -658,39 +702,45 @@ void
ConsumeWindowsEventLog::putEventRenderFlowFileToSession(const EventRender&
const std::string& str_;
};
- if (writeXML_) {
- auto flowFile = session.create();
- logger_->log_trace("Writing rendered XML to a flow file");
-
+ auto commitFlowFile = [&] (const std::shared_ptr<core::FlowFile>& flowFile,
const std::string& content, const std::string& mimeType) {
{
- WriteCallback wc{ eventRender.text_ };
+ WriteCallback wc{ content };
session.write(flowFile, &wc);
}
- for (const auto &fieldMapping : eventRender.matched_fields_) {
- if (!fieldMapping.second.empty()) {
- session.putAttribute(flowFile, fieldMapping.first,
fieldMapping.second);
- }
- }
- session.putAttribute(flowFile, core::SpecialFlowAttribute::MIME_TYPE,
"application/xml");
+ session.putAttribute(flowFile, core::SpecialFlowAttribute::MIME_TYPE,
mimeType);
session.putAttribute(flowFile, "Timezone name", timezone_name_);
session.putAttribute(flowFile, "Timezone offset", timezone_offset_);
session.getProvenanceReporter()->receive(flowFile, provenanceUri_,
getUUIDStr(), "Consume windows event logs", 0);
session.transfer(flowFile, Success);
- }
+ };
- if (writePlainText_) {
+ if (output_.xml) {
auto flowFile = session.create();
- logger_->log_trace("Writing rendered plain text to a flow file");
+ logger_->log_trace("Writing rendered XML to a flow file");
- {
- WriteCallback wc{ eventRender.rendered_text_ };
- session.write(flowFile, &wc);
+ for (const auto &fieldMapping : eventRender.matched_fields) {
+ if (!fieldMapping.second.empty()) {
+ session.putAttribute(flowFile, fieldMapping.first,
fieldMapping.second);
+ }
}
- session.putAttribute(flowFile, core::SpecialFlowAttribute::MIME_TYPE,
"text/plain");
- session.putAttribute(flowFile, "Timezone name", timezone_name_);
- session.putAttribute(flowFile, "Timezone offset", timezone_offset_);
- session.getProvenanceReporter()->receive(flowFile, provenanceUri_,
getUUIDStr(), "Consume windows event logs", 0);
- session.transfer(flowFile, Success);
+
+ commitFlowFile(flowFile, eventRender.xml, "application/xml");
+ }
+
+ if (output_.plaintext) {
+ logger_->log_trace("Writing rendered plain text to a flow file");
+ commitFlowFile(session.create(), eventRender.plaintext, "text/plain");
+ }
+
+ if (output_.json.type == JSONType::Raw) {
+ logger_->log_trace("Writing rendered raw JSON to a flow file");
+ commitFlowFile(session.create(), eventRender.json, "application/json");
+ } else if (output_.json.type == JSONType::Simple) {
+ logger_->log_trace("Writing rendered simple JSON to a flow file");
+ commitFlowFile(session.create(), eventRender.json, "application/json");
+ } else if (output_.json.type == JSONType::Flattened) {
+ logger_->log_trace("Writing rendered flattened JSON to a flow file");
+ commitFlowFile(session.create(), eventRender.json, "application/json");
}
}
diff --git a/extensions/windows-event-log/ConsumeWindowsEventLog.h
b/extensions/windows-event-log/ConsumeWindowsEventLog.h
index db4f13b..d0820bf 100644
--- a/extensions/windows-event-log/ConsumeWindowsEventLog.h
+++ b/extensions/windows-event-log/ConsumeWindowsEventLog.h
@@ -43,9 +43,10 @@ namespace minifi {
namespace processors {
struct EventRender {
- std::map<std::string, std::string> matched_fields_;
- std::string text_;
- std::string rendered_text_;
+ std::map<std::string, std::string> matched_fields;
+ std::string xml;
+ std::string plaintext;
+ std::string json;
};
class Bookmark;
@@ -77,6 +78,7 @@ public:
static core::Property EventHeaderDelimiter;
static core::Property EventHeader;
static core::Property OutputFormat;
+ static core::Property JSONFormat;
static core::Property BatchCommitSize;
static core::Property BookmarkRootDirectory;
static core::Property ProcessOldEvents;
@@ -107,9 +109,13 @@ protected:
bool createEventRender(EVT_HANDLE eventHandle, EventRender& eventRender);
void substituteXMLPercentageItems(pugi::xml_document& doc);
- static constexpr const char * const XML = "XML";
- static constexpr const char * const Both = "Both";
- static constexpr const char * const Plaintext = "Plaintext";
+ static constexpr const char* XML = "XML";
+ static constexpr const char* Both = "Both";
+ static constexpr const char* Plaintext = "Plaintext";
+ static constexpr const char* JSON = "JSON";
+ static constexpr const char* JSONRaw = "Raw";
+ static constexpr const char* JSONSimple = "Simple";
+ static constexpr const char* JSONFlattened = "Flattened";
private:
struct TimeDiff {
@@ -132,18 +138,30 @@ private:
std::wstring wstrChannel_;
std::wstring wstrQuery_;
std::string regex_;
- bool resolve_as_attributes_;
- bool apply_identifier_function_;
+ bool resolve_as_attributes_{false};
+ bool apply_identifier_function_{false};
std::string provenanceUri_;
std::string computerName_;
uint64_t maxBufferSize_{};
DWORD lastActivityTimestamp_{};
std::mutex cache_mutex_;
std::map<std::string, wel::WindowsEventLogHandler > providers_;
- uint64_t batch_commit_size_;
+ uint64_t batch_commit_size_{};
+
+ enum class JSONType {None, Raw, Simple, Flattened};
+
+ struct OutputFormat {
+ bool xml{false};
+ bool plaintext{false};
+ struct JSON {
+ JSONType type{JSONType::None};
+
+ explicit operator bool() const noexcept {
+ return type != JSONType::None;
+ }
+ } json;
+ } output_;
- bool writeXML_;
- bool writePlainText_;
std::unique_ptr<Bookmark> bookmark_;
std::mutex on_trigger_mutex_;
std::unordered_map<std::string, std::string> xmlPercentageItemsResolutions_;
diff --git a/extensions/windows-event-log/tests/CMakeLists.txt
b/extensions/windows-event-log/tests/CMakeLists.txt
index 4d144ce..1f8c9f3 100644
--- a/extensions/windows-event-log/tests/CMakeLists.txt
+++ b/extensions/windows-event-log/tests/CMakeLists.txt
@@ -17,7 +17,15 @@
# under the License.
#
-file(GLOB WEL_INTEGRATION_TESTS "*.cpp")
+set(WEL_INTEGRATION_TESTS "BookmarkTests.cpp"
"ConsumeWindowsEventLogTests.cpp" "MetadataWalkerTests.cpp")
+if (TEST_CUSTOM_WEL_PROVIDER)
+ execute_process(COMMAND
+
"${CMAKE_CURRENT_LIST_DIR}/custom-provider/generate-and-register.bat"
+ "${CMAKE_CURRENT_LIST_DIR}/custom-provider"
+ )
+ list(APPEND WEL_INTEGRATION_TESTS "CWELCustomProviderTests.cpp")
+endif()
+
SET(WEL_TEST_COUNT 0)
FOREACH(testfile ${WEL_INTEGRATION_TESTS})
get_filename_component(testfilename "${testfile}" NAME_WE)
diff --git a/extensions/windows-event-log/tests/CWELCustomProviderTests.cpp
b/extensions/windows-event-log/tests/CWELCustomProviderTests.cpp
new file mode 100644
index 0000000..57eb4bf
--- /dev/null
+++ b/extensions/windows-event-log/tests/CWELCustomProviderTests.cpp
@@ -0,0 +1,176 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef NDEBUG
+
+#include "ConsumeWindowsEventLog.h"
+
+#include "core/ConfigurableComponent.h"
+#include "processors/LogAttribute.h"
+#include "processors/PutFile.h"
+#include "TestBase.h"
+#include "utils/TestUtils.h"
+#include "utils/file/FileUtils.h"
+#include "rapidjson/document.h"
+#include "wel/UniqueEvtHandle.h"
+#include "IntegrationTestUtils.h"
+
+#include "CWELTestUtils.h"
+
+// generated from the manifest file "custom-provider/unit-test-provider.man"
+// using the command "mc -um unit-test-provider.man"
+#include "custom-provider/unit-test-provider.h"
+
+namespace {
+
+struct CustomEventData {
+ std::wstring first;
+ std::wstring second;
+ std::wstring third;
+ int binary_length;
+ const unsigned char* binary_data;
+};
+
+const std::string CUSTOM_PROVIDER_NAME = "minifi_unit_test_provider";
+const std::string CUSTOM_CHANNEL = CUSTOM_PROVIDER_NAME + "/Log";
+
+bool dispatchCustomEvent(const CustomEventData& event) {
+ static auto provider_initialized = EventRegisterminifi_unit_test_provider();
+ REQUIRE(provider_initialized == ERROR_SUCCESS);
+
+ auto result = EventWriteCustomEvent(
+ event.first.c_str(),
+ event.second.c_str(),
+ event.third.c_str(),
+ event.binary_length,
+ event.binary_data
+ );
+ return result == ERROR_SUCCESS;
+}
+
+using org::apache::nifi::minifi::wel::unique_evt_handle;
+
+bool advanceBookmark(const unique_evt_handle& hBookmark, const std::string&
channel, const std::string& query, bool advance_to_last = false) {
+ const auto hEventResults = unique_evt_handle{ EvtQuery(0,
std::wstring{channel.begin(), channel.end()}.c_str(),
std::wstring{query.begin(), query.end()}.c_str(), EvtQueryChannelPath) };
+ if (!hEventResults) {
+ return false;
+ }
+
+ if (advance_to_last) {
+ if (!EvtSeek(hEventResults.get(), 0, 0, 0, EvtSeekRelativeToLast)) {
+ return false;
+ }
+ } else {
+ if (!EvtSeek(hEventResults.get(), 1, hBookmark.get(), 0,
EvtSeekRelativeToBookmark)) {
+ return false;
+ }
+ }
+
+ const unique_evt_handle hEvent = [&hEventResults] {
+ DWORD dwReturned{};
+ EVT_HANDLE hEvent{ nullptr };
+ EvtNext(hEventResults.get(), 1, &hEvent, INFINITE, 0, &dwReturned);
+ return unique_evt_handle{ hEvent };
+ }();
+
+ if (!hEvent) {
+ return false;
+ }
+
+ REQUIRE(EvtUpdateBookmark(hBookmark.get(), hEvent.get()));
+
+ return true;
+}
+
+class CustomProviderController : public OutputFormatTestController {
+ public:
+ CustomProviderController(std::string format, std::string json_format) :
OutputFormatTestController(CUSTOM_CHANNEL, "*", std::move(format),
std::move(json_format)) {
+ bookmark_.reset(EvtCreateBookmark(0));
+ advanceBookmark(bookmark_, channel_, query_, true);
+ REQUIRE(bookmark_);
+ }
+
+ protected:
+ void dispatchBookmarkEvent() override {
+ auto binary = reinterpret_cast<const unsigned char*>("\x0c\x10");
+ REQUIRE(dispatchCustomEvent({L"Bookmark", L"Second", L"Third", 2,
binary}));
+ REQUIRE(checkNewEventAvailable());
+ }
+ void dispatchCollectedEvent() override {
+ auto binary = reinterpret_cast<const unsigned char*>("\x09\x01");
+ REQUIRE(dispatchCustomEvent({L"Actual event", L"Second", L"Third", 2,
binary}));
+ REQUIRE(checkNewEventAvailable());
+ }
+
+ private:
+ bool checkNewEventAvailable() {
+ return
org::apache::nifi::minifi::utils::verifyEventHappenedInPollTime(std::chrono::seconds{5},
[&] {
+ return advanceBookmark(bookmark_, channel_, query_);
+ });
+ }
+ unique_evt_handle bookmark_;
+};
+
+const std::string EVENT_DATA_JSON = R"(
+ [{
+ "Type": "Data",
+ "Content": "Actual event",
+ "Name": "param1"
+ }, {
+ "Type": "Data",
+ "Content": "Second",
+ "Name": "param2"
+ }, {
+ "Type": "Data",
+ "Content": "Third",
+ "Name": "Channel"
+ }, {
+ "Type": "Binary",
+ "Content": "0901",
+ "Name": ""
+ }]
+)";
+
+} // namespace
+
+TEST_CASE("ConsumeWindowsEventLog prints events in JSON::Simple correctly
custom provider", "[onTrigger]") {
+ std::string event = CustomProviderController{"JSON", "Simple"}.run();
+ verifyJSON(event, R"(
+ {
+ "System": {
+ "Provider": {
+ "Name": ")" + CUSTOM_PROVIDER_NAME + R"("
+ },
+ "Channel": ")" + CUSTOM_CHANNEL + R"("
+ },
+ "EventData": )" + EVENT_DATA_JSON + R"(
+ }
+ )");
+}
+
+TEST_CASE("ConsumeWindowsEventLog prints events in JSON::Flattened correctly
custom provider", "[onTrigger]") {
+ std::string event = CustomProviderController{"JSON", "Flattened"}.run();
+ verifyJSON(event, R"(
+ {
+ "Name": ")" + CUSTOM_PROVIDER_NAME + R"(",
+ "Channel": ")" + CUSTOM_CHANNEL /* Channel is not overwritten by data
named "Channel" */ + R"(",
+ "EventData": )" + EVENT_DATA_JSON /* EventData is not discarded */ + R"(,
+ "param1": "Actual event",
+ "param2": "Second"
+ }
+ )");
+}
diff --git a/extensions/windows-event-log/tests/CWELTestUtils.h
b/extensions/windows-event-log/tests/CWELTestUtils.h
new file mode 100644
index 0000000..3820ca5
--- /dev/null
+++ b/extensions/windows-event-log/tests/CWELTestUtils.h
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "ConsumeWindowsEventLog.h"
+#include "processors/PutFile.h"
+#include "TestBase.h"
+#include "utils/TestUtils.h"
+#include "utils/file/FileUtils.h"
+#include "utils/OptionalUtils.h"
+
+core::Relationship Success{"success", "Everything is fine"};
+
+using ConsumeWindowsEventLog =
org::apache::nifi::minifi::processors::ConsumeWindowsEventLog;
+using PutFile = org::apache::nifi::minifi::processors::PutFile;
+
+class OutputFormatTestController : public TestController {
+ public:
+ OutputFormatTestController(std::string channel, std::string query,
std::string output_format, utils::optional<std::string> json_format = {})
+ : channel_(std::move(channel)),
+ query_(std::move(query)),
+ output_format_(std::move(output_format)),
+ json_format_(std::move(json_format)) {}
+
+ std::string run() {
+ LogTestController::getInstance().setDebug<ConsumeWindowsEventLog>();
+ LogTestController::getInstance().setDebug<PutFile>();
+ std::shared_ptr<TestPlan> test_plan = createPlan();
+
+ auto cwel_processor = test_plan->addProcessor("ConsumeWindowsEventLog",
"cwel");
+ test_plan->setProperty(cwel_processor,
ConsumeWindowsEventLog::Channel.getName(), channel_);
+ test_plan->setProperty(cwel_processor,
ConsumeWindowsEventLog::Query.getName(), query_);
+ test_plan->setProperty(cwel_processor,
ConsumeWindowsEventLog::OutputFormat.getName(), output_format_);
+ if (json_format_) {
+ test_plan->setProperty(cwel_processor,
ConsumeWindowsEventLog::JSONFormat.getName(), json_format_.value());
+ }
+
+ auto dir = utils::createTempDir(this);
+
+ auto put_file = test_plan->addProcessor("PutFile", "putFile", Success,
true);
+ test_plan->setProperty(put_file, PutFile::Directory.getName(), dir);
+
+ {
+ dispatchBookmarkEvent();
+
+ runSession(test_plan);
+ }
+
+ test_plan->reset();
+
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+
+
+ {
+ dispatchCollectedEvent();
+
+ runSession(test_plan);
+
+ auto files = utils::file::list_dir_all(dir,
LogTestController::getInstance().getLogger<LogTestController>(), false);
+ REQUIRE(files.size() == 1);
+
+ std::ifstream file{utils::file::concat_path(files[0].first,
files[0].second)};
+ return {std::istreambuf_iterator<char>{file}, {}};
+ }
+ }
+
+ protected:
+ virtual void dispatchBookmarkEvent() = 0;
+ virtual void dispatchCollectedEvent() = 0;
+
+ std::string channel_;
+ std::string query_;
+ std::string output_format_;
+ utils::optional<std::string> json_format_;
+};
+
+// carries out a loose match on objects, i.e. it doesn't matter if the
+// actual object has extra fields than expected
+void matchJSON(const rapidjson::Value& json, const rapidjson::Value& expected)
{
+ if (expected.IsObject()) {
+ REQUIRE(json.IsObject());
+ for (const auto& expected_member : expected.GetObject()) {
+ REQUIRE(json.HasMember(expected_member.name));
+ matchJSON(json[expected_member.name], expected_member.value);
+ }
+ } else if (expected.IsArray()) {
+ REQUIRE(json.IsArray());
+ REQUIRE(json.Size() == expected.Size());
+ for (size_t idx{0}; idx < expected.Size(); ++idx) {
+ matchJSON(json[idx], expected[idx]);
+ }
+ } else {
+ REQUIRE(json == expected);
+ }
+}
+
+void verifyJSON(const std::string& json_str, const std::string& expected_str) {
+ rapidjson::Document json, expected;
+ REQUIRE_FALSE(json.Parse(json_str.c_str()).HasParseError());
+ REQUIRE_FALSE(expected.Parse(expected_str.c_str()).HasParseError());
+
+ matchJSON(json, expected);
+}
diff --git a/extensions/windows-event-log/tests/ConsumeWindowsEventLogTests.cpp
b/extensions/windows-event-log/tests/ConsumeWindowsEventLogTests.cpp
index 02aae26..1976f40 100644
--- a/extensions/windows-event-log/tests/ConsumeWindowsEventLogTests.cpp
+++ b/extensions/windows-event-log/tests/ConsumeWindowsEventLogTests.cpp
@@ -19,17 +19,22 @@
#include "core/ConfigurableComponent.h"
#include "processors/LogAttribute.h"
+#include "processors/PutFile.h"
#include "TestBase.h"
+#include "utils/TestUtils.h"
+#include "utils/file/FileUtils.h"
+#include "rapidjson/document.h"
+
+#include "CWELTestUtils.h"
using ConsumeWindowsEventLog =
org::apache::nifi::minifi::processors::ConsumeWindowsEventLog;
using LogAttribute = org::apache::nifi::minifi::processors::LogAttribute;
+using PutFile = org::apache::nifi::minifi::processors::PutFile;
using ConfigurableComponent =
org::apache::nifi::minifi::core::ConfigurableComponent;
using IdGenerator = org::apache::nifi::minifi::utils::IdGenerator;
namespace {
-core::Relationship Success{"success", "Everything is fine"};
-
const std::string APPLICATION_CHANNEL = "Application";
constexpr DWORD CWEL_TESTS_OPCODE = 14985; // random opcode hopefully won't
clash with something important
@@ -41,6 +46,19 @@ void reportEvent(const std::string& channel, const char*
message, WORD log_level
ReportEventA(event_source, log_level, 0, CWEL_TESTS_OPCODE, nullptr, 1, 0,
&message, nullptr);
}
+class SimpleFormatTestController : public OutputFormatTestController {
+ public:
+ using OutputFormatTestController::OutputFormatTestController;
+
+ protected:
+ void dispatchBookmarkEvent() {
+ reportEvent(APPLICATION_CHANNEL, "Event zero: this is in the past");
+ }
+ void OutputFormatTestController::dispatchCollectedEvent() {
+ reportEvent(APPLICATION_CHANNEL, "Event one");
+ }
+};
+
} // namespace
TEST_CASE("ConsumeWindowsEventLog constructor works", "[create]") {
@@ -305,48 +323,75 @@ TEST_CASE("ConsumeWindowsEventLog output format can be
set", "[create][output_fo
// TEST_CASE("ConsumeWindowsEventLog prints events in plain text correctly",
"[onTrigger]")
TEST_CASE("ConsumeWindowsEventLog prints events in XML correctly",
"[onTrigger]") {
- TestController test_controller;
- LogTestController::getInstance().setDebug<ConsumeWindowsEventLog>();
- LogTestController::getInstance().setDebug<LogAttribute>();
- std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
-
- auto cwel_processor = test_plan->addProcessor("ConsumeWindowsEventLog",
"cwel");
- test_plan->setProperty(cwel_processor,
ConsumeWindowsEventLog::Channel.getName(), APPLICATION_CHANNEL);
- test_plan->setProperty(cwel_processor,
ConsumeWindowsEventLog::Query.getName(), QUERY);
- test_plan->setProperty(cwel_processor,
ConsumeWindowsEventLog::OutputFormat.getName(), "XML");
-
- auto logger_processor = test_plan->addProcessor("LogAttribute", "logger",
Success, true);
- test_plan->setProperty(logger_processor,
LogAttribute::FlowFilesToLog.getName(), "0");
- test_plan->setProperty(logger_processor, LogAttribute::LogPayload.getName(),
"true");
- test_plan->setProperty(logger_processor,
LogAttribute::MaxPayloadLineLength.getName(), "1024");
-
- {
- reportEvent(APPLICATION_CHANNEL, "Event zero: this is in the past");
-
- test_controller.runSession(test_plan);
- }
-
- test_plan->reset();
-
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+ std::string event = SimpleFormatTestController{APPLICATION_CHANNEL, QUERY,
"XML"}.run();
+
+ REQUIRE(event.find(R"(<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event"><System><Provider
Name="Application"/>)") != std::string::npos);
+ REQUIRE(event.find(R"(<EventID Qualifiers="0">14985</EventID>)") !=
std::string::npos);
+ REQUIRE(event.find(R"(<Level>4</Level>)") != std::string::npos);
+ REQUIRE(event.find(R"(<Task>0</Task>)") != std::string::npos);
+ REQUIRE(event.find(R"(<Keywords>0x80000000000000</Keywords><TimeCreated
SystemTime=")") != std::string::npos);
+ // the timestamp (when the event was published) goes here
+ REQUIRE(event.find(R"("/><EventRecordID>)") != std::string::npos);
+ // the ID of the event goes here (a number)
+ REQUIRE(event.find(R"(</EventRecordID>)") != std::string::npos);
+ REQUIRE(event.find(R"(<Channel>Application</Channel><Computer>)") !=
std::string::npos);
+ // the computer name goes here
+ REQUIRE(event.find(R"(</Computer><Security/></System><EventData><Data>Event
one</Data></EventData></Event>)") != std::string::npos);
+}
- {
- reportEvent(APPLICATION_CHANNEL, "Event one");
+TEST_CASE("ConsumeWindowsEventLog prints events in JSON::Simple correctly",
"[onTrigger]") {
+ std::string event = SimpleFormatTestController{APPLICATION_CHANNEL, "*",
"JSON", "Simple"}.run();
+ verifyJSON(event, R"json(
+ {
+ "System": {
+ "Provider": {
+ "Name": "Application"
+ },
+ "Channel": "Application"
+ },
+ "EventData": [{
+ "Type": "Data",
+ "Content": "Event one",
+ "Name": ""
+ }]
+ }
+ )json");
+}
- test_controller.runSession(test_plan);
+TEST_CASE("ConsumeWindowsEventLog prints events in JSON::Flattened correctly",
"[onTrigger]") {
+ std::string event = SimpleFormatTestController{APPLICATION_CHANNEL, "*",
"JSON", "Flattened"}.run();
+ verifyJSON(event, R"json(
+ {
+ "Name": "Application",
+ "Channel": "Application",
+ "EventData": [{
+ "Type": "Data",
+ "Content": "Event one",
+ "Name": ""
+ }]
+ }
+ )json");
+}
- REQUIRE(LogTestController::getInstance().contains(R"(<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event"><System><Provider
Name="Application"/>)"));
- REQUIRE(LogTestController::getInstance().contains(R"(<EventID
Qualifiers="0">14985</EventID>)"));
- REQUIRE(LogTestController::getInstance().contains(R"(<Level>4</Level>)"));
- REQUIRE(LogTestController::getInstance().contains(R"(<Task>0</Task>)"));
-
REQUIRE(LogTestController::getInstance().contains(R"(<Keywords>0x80000000000000</Keywords><TimeCreated
SystemTime=")"));
- // the timestamp (when the event was published) goes here
-
REQUIRE(LogTestController::getInstance().contains(R"("/><EventRecordID>)"));
- // the ID of the event goes here (a number)
- REQUIRE(LogTestController::getInstance().contains(R"(</EventRecordID>)"));
-
REQUIRE(LogTestController::getInstance().contains(R"(<Channel>Application</Channel><Computer>)"));
- // the computer name goes here
-
REQUIRE(LogTestController::getInstance().contains(R"(</Computer><Security/></System><EventData><Data>Event
one</Data></EventData></Event>)"));
- }
+TEST_CASE("ConsumeWindowsEventLog prints events in JSON::Raw correctly",
"[onTrigger]") {
+ std::string event = SimpleFormatTestController{APPLICATION_CHANNEL, "*",
"JSON", "Raw"}.run();
+ verifyJSON(event, R"json(
+ [
+ {
+ "name": "Event",
+ "children": [
+ {"name": "System"},
+ {
+ "name": "EventData",
+ "children": [{
+ "name": "Data",
+ "text": "Event one"
+ }]
+ }
+ ]
+ }
+ ]
+ )json");
}
namespace {
diff --git
a/extensions/windows-event-log/tests/custom-provider/generate-and-register.bat
b/extensions/windows-event-log/tests/custom-provider/generate-and-register.bat
new file mode 100644
index 0000000..c74090b
--- /dev/null
+++
b/extensions/windows-event-log/tests/custom-provider/generate-and-register.bat
@@ -0,0 +1,62 @@
+@echo off &setlocal enabledelayedexpansion
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements. See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License. You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+cd %1
+
+(
+ echo ^<?xml version="1.0" encoding="UTF-8"?^>
+ echo ^<instrumentationManifest
xsi:schemaLocation="http://schemas.microsoft.com/win/2004/08/events
eventman.xsd"
+ echo xmlns="http://schemas.microsoft.com/win/2004/08/events"
+ echo xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events"
+ echo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ echo xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ echo xmlns:trace="http://schemas.microsoft.com/win/2004/08/events/trace"^>
+ echo ^<instrumentation^>
+ echo ^<events^>
+ echo ^<provider name="minifi_unit_test_provider"
+ echo symbol="minifi_unit_test_provider"
+ echo guid="{ABCDEF01-8174-F1CA-87BE-DA129FF6001B}"
+ echo resourceFileName="%1\unit-test-provider.dll"
+ echo messageFileName="%1\unit-test-provider.dll"^>
+ echo ^<events^>
+ echo ^<event symbol="CustomEvent" value="10000" version="1"
channel="minifi_unit_test_provider/Log" template="CustomTemplate" /^>
+ echo ^</events^>
+ echo ^<levels/^>
+ echo ^<tasks/^>
+ echo ^<opcodes/^>
+ echo ^<channels^>
+ echo ^<channel name="minifi_unit_test_provider/Log" value="0x10"
type="Operational" enabled="true" /^>
+ echo ^</channels^>
+ echo ^<templates^>
+ echo ^<template tid="CustomTemplate"^>
+ echo ^<data name="param1" inType="win:UnicodeString"
outType="xs:string" /^>
+ echo ^<data name="param2" inType="win:UnicodeString"
outType="xs:string" /^>
+ echo ^<data name="Channel" inType="win:UnicodeString"
outType="xs:string" /^>
+ echo ^<binary /^>
+ echo ^</template^>
+ echo ^</templates^>
+ echo ^</provider^>
+ echo ^</events^>
+ echo ^</instrumentation^>
+ echo ^<localization/^>
+ echo ^</instrumentationManifest^>
+) > "%1/unit-test-provider.man"
+
+mc -css Namespace unit-test-provider.man
+mc -um unit-test-provider.man
+rc unit-test-provider.rc
+csc /target:library /unsafe /win32res:unit-test-provider.res
unit-test-provider.cs
+wevtutil im unit-test-provider.man
diff --git a/extensions/windows-event-log/wel/JSONUtils.cpp
b/extensions/windows-event-log/wel/JSONUtils.cpp
new file mode 100644
index 0000000..4e59de7
--- /dev/null
+++ b/extensions/windows-event-log/wel/JSONUtils.cpp
@@ -0,0 +1,170 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JSONUtils.h"
+
+#include <vector>
+#include <string>
+#include <functional>
+
+#include <pugixml.hpp>
+
+#include "rapidjson/document.h"
+#include "rapidjson/writer.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/prettywriter.h"
+
+#include "gsl/gsl-lite.hpp";
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace wel {
+
+namespace {
+
+rapidjson::Value xmlElementToJSON(const pugi::xml_node& node,
rapidjson::Document& doc) {
+ gsl_Expects(node.type() == pugi::xml_node_type::node_element);
+ rapidjson::Value object(rapidjson::kObjectType);
+ object.AddMember("name", rapidjson::StringRef(node.name()),
doc.GetAllocator());
+ auto& attributes = object.AddMember("attributes", rapidjson::kObjectType,
doc.GetAllocator())["attributes"];
+ for (const auto& attr : node.attributes()) {
+ attributes.AddMember(rapidjson::StringRef(attr.name()),
rapidjson::StringRef(attr.value()), doc.GetAllocator());
+ }
+ auto& children = object.AddMember("children", rapidjson::kArrayType,
doc.GetAllocator())["children"];
+ for (const auto& child : node.children()) {
+ if (child.type() == pugi::xml_node_type::node_element) {
+ children.PushBack(xmlElementToJSON(child, doc), doc.GetAllocator());
+ }
+ }
+ object.AddMember("text", rapidjson::StringRef(node.text().get()),
doc.GetAllocator());
+ return object;
+}
+
+rapidjson::Value xmlDocumentToJSON(const pugi::xml_node& node,
rapidjson::Document& doc) {
+ gsl_Expects(node.type() == pugi::xml_node_type::node_document);
+ rapidjson::Value children(rapidjson::kArrayType);
+ for (const auto& child : node.children()) {
+ if (child.type() == pugi::xml_node_type::node_element) {
+ children.PushBack(xmlElementToJSON(child, doc), doc.GetAllocator());
+ }
+ }
+ return children;
+}
+
+rapidjson::Document toJSONImpl(const pugi::xml_node& root, bool flatten) {
+ rapidjson::Document doc{rapidjson::kObjectType};
+
+ auto event_xml = root.child("Event");
+
+ {
+ auto system_xml = event_xml.child("System");
+ auto& system = flatten ? doc : doc.AddMember("System",
rapidjson::kObjectType, doc.GetAllocator())["System"];
+
+ {
+ auto provider_xml = system_xml.child("Provider");
+ auto& provider = flatten ? doc : system.AddMember("Provider",
rapidjson::kObjectType, doc.GetAllocator())["Provider"];
+ provider.AddMember("Name",
rapidjson::StringRef(provider_xml.attribute("Name").value()),
doc.GetAllocator());
+ provider.AddMember("Guid",
rapidjson::StringRef(provider_xml.attribute("Guid").value()),
doc.GetAllocator());
+ }
+
+ system.AddMember("EventID",
rapidjson::StringRef(system_xml.child("EventID").text().get()),
doc.GetAllocator());
+ system.AddMember("Version",
rapidjson::StringRef(system_xml.child("Version").text().get()),
doc.GetAllocator());
+ system.AddMember("Level",
rapidjson::StringRef(system_xml.child("Level").text().get()),
doc.GetAllocator());
+ system.AddMember("Task",
rapidjson::StringRef(system_xml.child("Task").text().get()),
doc.GetAllocator());
+ system.AddMember("Opcode",
rapidjson::StringRef(system_xml.child("Opcode").text().get()),
doc.GetAllocator());
+ system.AddMember("Keywords",
rapidjson::StringRef(system_xml.child("Keywords").text().get()),
doc.GetAllocator());
+
+ {
+ auto timeCreated_xml = system_xml.child("TimeCreated");
+ auto& timeCreated = flatten ? doc : system.AddMember("TimeCreated",
rapidjson::kObjectType, doc.GetAllocator())["TimeCreated"];
+ timeCreated.AddMember("SystemTime",
rapidjson::StringRef(timeCreated_xml.attribute("SystemTime").value()),
doc.GetAllocator());
+ }
+
+ system.AddMember("EventRecordID",
rapidjson::StringRef(system_xml.child("EventRecordID").text().get()),
doc.GetAllocator());
+
+ {
+ auto correlation_xml = system_xml.child("Correlation");
+ auto& correlation = flatten ? doc : system.AddMember("Correlation",
rapidjson::kObjectType, doc.GetAllocator())["Correlation"];
+ correlation.AddMember("ActivityID",
rapidjson::StringRef(correlation_xml.attribute("ActivityID").value()),
doc.GetAllocator());
+ }
+
+ {
+ auto execution_xml = system_xml.child("Execution");
+ auto& execution = flatten ? doc : system.AddMember("Execution",
rapidjson::kObjectType, doc.GetAllocator())["Execution"];
+ execution.AddMember("ProcessID",
rapidjson::StringRef(execution_xml.attribute("ProcessID").value()),
doc.GetAllocator());
+ execution.AddMember("ThreadID",
rapidjson::StringRef(execution_xml.attribute("ThreadID").value()),
doc.GetAllocator());
+ }
+
+ system.AddMember("Channel",
rapidjson::StringRef(system_xml.child("Channel").text().get()),
doc.GetAllocator());
+ system.AddMember("Computer",
rapidjson::StringRef(system_xml.child("Computer").text().get()),
doc.GetAllocator());
+ }
+
+ {
+ auto eventData_xml = event_xml.child("EventData");
+ // create EventData subarray even if flatten requested
+ doc.AddMember("EventData", rapidjson::kArrayType, doc.GetAllocator());
+ for (const auto& data : eventData_xml.children()) {
+ auto name_attr = data.attribute("Name");
+ rapidjson::Value item(rapidjson::kObjectType);
+ item.AddMember("Name", rapidjson::StringRef(name_attr.value()),
doc.GetAllocator());
+ item.AddMember("Content", rapidjson::StringRef(data.text().get()),
doc.GetAllocator());
+ item.AddMember("Type", rapidjson::StringRef(data.name()),
doc.GetAllocator());
+ // we need to query EventData because a reference to it wouldn't be
stable, as we
+ // possibly add members to its parent which could result in reallocation
+ doc["EventData"].PushBack(item, doc.GetAllocator());
+ // check collision
+ if (flatten && !name_attr.empty() && !doc.HasMember(name_attr.value())) {
+ doc.AddMember(rapidjson::StringRef(name_attr.value()),
rapidjson::StringRef(data.text().get()), doc.GetAllocator());
+ }
+ }
+ }
+
+ return doc;
+}
+
+} // namespace
+
+rapidjson::Document toRawJSON(const pugi::xml_node& root) {
+ rapidjson::Document doc;
+ if (root.type() == pugi::xml_node_type::node_document) {
+ static_cast<rapidjson::Value&>(doc) = xmlDocumentToJSON(root, doc);
+ }
+ return doc;
+}
+
+rapidjson::Document toSimpleJSON(const pugi::xml_node& root) {
+ return toJSONImpl(root, false);
+}
+
+rapidjson::Document toFlattenedJSON(const pugi::xml_node& root) {
+ return toJSONImpl(root, true);
+}
+
+std::string jsonToString(rapidjson::Document& doc) {
+ rapidjson::StringBuffer buffer;
+ rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
+ doc.Accept(writer);
+ return buffer.GetString();
+}
+
+} // namespace wel
+} // namespace minifi
+} // namespace nifi
+} // namespace apache
+} // namespace org
diff --git a/extensions/windows-event-log/wel/JSONUtils.h
b/extensions/windows-event-log/wel/JSONUtils.h
new file mode 100644
index 0000000..5b3b8ab
--- /dev/null
+++ b/extensions/windows-event-log/wel/JSONUtils.h
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#undef RAPIDJSON_ASSERT
+#define RAPIDJSON_ASSERT(x) if (!(x)) throw std::logic_error("rapidjson
exception"); // NOLINT
+
+#include <pugixml.hpp>
+
+#include <stdexcept> // for std::logic_error
+#include "rapidjson/document.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace wel {
+
+/**
+ * * !!WARNING!! The json document must not outlive the xml argument. For
better performance,
+ * the created json document stores references to values in the xml node.
Accessing the
+ * json document after the xml node has been changed or destroyed results in
undefined behavior.
+ *
+ * Converts each xml element node to a json object of
+ * the form {name: String, attributes: Object, children: Array, text: String}
+ * Aims to preserve most of the input xml structure.
+ */
+rapidjson::Document toRawJSON(const pugi::xml_node& root);
+
+/**
+ * * !!WARNING!! The json document must not outlive the xml argument. For
better performance,
+ * the created json document stores references to values in the xml node.
Accessing the
+ * json document after the xml node has been changed or destroyed results in
undefined behavior.
+ *
+ * Retains some hierarchical structure of the original xml event,
+ * e.g. transforms
+ * <Event><System><Provider Name="Banana" Guid="{5}"/></System></Event>
+ * into
+ * {System: {Provider: {Name: "Banana", Guid: "{5}"}}}
+ */
+rapidjson::Document toSimpleJSON(const pugi::xml_node& root);
+
+/**
+ * * !!WARNING!! The json document must not outlive the xml argument. For
better performance,
+ * the created json document stores references to values in the xml node.
Accessing the
+ * json document after the xml node has been changed or destroyed results in
undefined behavior.
+ *
+ * Flattens most of the structure, i.e. removes intermediate
+ * objects and lifts innermost string-valued keys to the root.
+ * e.g. {System: {Provider: {Name: String}}} => {Name: String}
+ *
+ * Moreover it also flattens each named data element where the
+ * name does not conflict with already existing members
+ * (e.g. a data with name "Guid" won't be flattened as it would
+ * overwrite the existing "Guid" field).
+ *
+ * e.g. {EventData: [{Name: "Test", Content: "X"}]} => {Test: "X"}
+ *
+ * In order to mitigate data loss, it preserves the EventData
+ * array in its entirety as well.
+ * (otherwise a "Guid" data would be lost)
+ */
+rapidjson::Document toFlattenedJSON(const pugi::xml_node& root);
+
+std::string jsonToString(rapidjson::Document& doc);
+
+} // namespace wel
+} // namespace minifi
+} // namespace nifi
+} // namespace apache
+} // namespace org
diff --git a/extensions/windows-event-log/wel/MetadataWalker.cpp
b/extensions/windows-event-log/wel/MetadataWalker.cpp
index 2c8e682..0f4f46f 100644
--- a/extensions/windows-event-log/wel/MetadataWalker.cpp
+++ b/extensions/windows-event-log/wel/MetadataWalker.cpp
@@ -79,7 +79,10 @@ bool MetadataWalker::for_each(pugi::xml_node &node) {
metadata_["EventID"] = node.text().get();
}
else {
- static std::map<std::string, EVT_FORMAT_MESSAGE_FLAGS> formatFlagMap = {
{"Channel", EvtFormatMessageChannel}, {"Keywords", EvtFormatMessageKeyword},
{"Level", EvtFormatMessageLevel}, {"Opcode", EvtFormatMessageOpcode},
{"Task",EvtFormatMessageTask} };
+ static std::map<std::string, EVT_FORMAT_MESSAGE_FLAGS> formatFlagMap = {
+ {"Channel", EvtFormatMessageChannel}, {"Keywords",
EvtFormatMessageKeyword}, {"Level", EvtFormatMessageLevel},
+ {"Opcode", EvtFormatMessageOpcode}, {"Task",EvtFormatMessageTask}
+ };
auto it = formatFlagMap.find(node_name);
if (it != formatFlagMap.end()) {
std::function<std::string(const std::string &)> updateFunc = [&](const
std::string &input) -> std::string {
diff --git a/extensions/windows-event-log/wel/WindowsEventLog.cpp
b/extensions/windows-event-log/wel/WindowsEventLog.cpp
index 8d1334b..80fb3c1 100644
--- a/extensions/windows-event-log/wel/WindowsEventLog.cpp
+++ b/extensions/windows-event-log/wel/WindowsEventLog.cpp
@@ -148,6 +148,11 @@ std::string
WindowsEventLogMetadataImpl::getEventData(EVT_FORMAT_MESSAGE_FLAGS f
EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, NULL, flags,
num_chars_in_buffer, buffer.get(), &num_chars_used);
}
}
+
+ if (num_chars_used == 0) {
+ return event_data;
+ }
+
if (EvtFormatMessageKeyword == flags) {
buffer.get()[num_chars_used - 1] = L'\0';
}
diff --git a/win_build_vs.bat b/win_build_vs.bat
index 9335e08..222f2df 100755
--- a/win_build_vs.bat
+++ b/win_build_vs.bat
@@ -28,6 +28,7 @@ set build_coap=OFF
set build_jni=OFF
set build_SQL=OFF
set build_AWS=OFF
+set test_custom_wel_provider=OFF
set generator="Visual Studio 15 2017"
set cpack=OFF
set installer_merge_modules=OFF
@@ -50,14 +51,14 @@ for %%x in (%*) do (
if [%%~x] EQU [/64] set build_platform=x64
if [%%~x] EQU [/D] set cmake_build_type=RelWithDebInfo
if [%%~x] EQU [/DD] set cmake_build_type=Debug
- if [%%~x] EQU [/CI] set
"strict_gsl_checks=-DSTRICT_GSL_CHECKS=AUDIT"
+ if [%%~x] EQU [/CI] set
"strict_gsl_checks=-DSTRICT_GSL_CHECKS=AUDIT" & set test_custom_wel_provider=ON
if [%%~x] EQU [/NONFREEUCRT] set "redist=-DMSI_REDISTRIBUTE_UCRT_NONASL=ON"
)
mkdir %builddir%
pushd %builddir%\
-cmake -G %generator% -A %build_platform%
-DINSTALLER_MERGE_MODULES=%installer_merge_modules% -DENABLE_SQL=%build_SQL%
-DCMAKE_BUILD_TYPE_INIT=%cmake_build_type%
-DCMAKE_BUILD_TYPE=%cmake_build_type% -DWIN32=WIN32
-DENABLE_LIBRDKAFKA=%build_kafka% -DENABLE_JNI=%build_jni% -DOPENSSL_OFF=OFF
-DENABLE_COAP=%build_coap% -DENABLE_AWS=%build_AWS% -DUSE_SHARED_LIBS=OFF
-DDISABLE_CONTROLLER=ON -DBUILD_ROCKSDB=ON -DFORCE_WINDOWS=ON
-DUSE_SYSTEM_UUID=OFF -DDISABLE_LIBARCHIVE=OFF -DDISABLE_SCRIPTIN [...]
+cmake -G %generator% -A %build_platform%
-DINSTALLER_MERGE_MODULES=%installer_merge_modules%
-DTEST_CUSTOM_WEL_PROVIDER=%test_custom_wel_provider% -DENABLE_SQL=%build_SQL%
-DCMAKE_BUILD_TYPE_INIT=%cmake_build_type%
-DCMAKE_BUILD_TYPE=%cmake_build_type% -DWIN32=WIN32
-DENABLE_LIBRDKAFKA=%build_kafka% -DENABLE_JNI=%build_jni% -DOPENSSL_OFF=OFF
-DENABLE_COAP=%build_coap% -DENABLE_AWS=%build_AWS% -DUSE_SHARED_LIBS=OFF
-DDISABLE_CONTROLLER=ON -DBUILD_ROCKSDB=ON -DFORCE_WINDOWS=ON -DUSE_SYSTE [...]
IF %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
if [%cpack%] EQU [ON] (
cpack -C %cmake_build_type%