This is an automated email from the ASF dual-hosted git repository.

fgerlits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 01a5665c98aeae373fc537cc3bb81b8578eed7c6
Author: Martin Zink <[email protected]>
AuthorDate: Fri Sep 30 16:45:50 2022 +0200

    MINIFICPP-1949 ConsumeWindowsEventLog should have the identifier regex 
compiled during onSchedule
    
    also: MINIFICPP-1956 ConsumeWindowsEventLog plaintext out should contain 
the EvtFormatMessage errors
    
    add checks for malloc
    readded identifier replacement in plain text
    refactored GetEventTimestampStr
    refactored getWindowsErrorAsString to windowsErrorToErrorCode
    refactored MetadataWalker::regex_ from optional& to raw pointer
    changed std::format to fmt::format
    
    Signed-off-by: Ferenc Gerlits <[email protected]>
    This closes #1434
---
 extensions/windows-event-log/Bookmark.cpp          |  67 ++++----
 extensions/windows-event-log/Bookmark.h            |  15 +-
 .../windows-event-log/ConsumeWindowsEventLog.cpp   | 179 +++++++++++----------
 .../windows-event-log/ConsumeWindowsEventLog.h     |  77 +++++----
 .../windows-event-log/tests/BookmarkTests.cpp      |   6 +-
 extensions/windows-event-log/tests/CWELTestUtils.h |   4 +-
 .../tests/ConsumeWindowsEventLogTests.cpp          | 135 +++++++++-------
 .../tests/MetadataWalkerTests.cpp                  |  63 +++++---
 .../windows-event-log/wel/MetadataWalker.cpp       |  37 +----
 extensions/windows-event-log/wel/MetadataWalker.h  |  37 ++---
 .../windows-event-log/wel/WindowsEventLog.cpp      | 134 ++++++++-------
 extensions/windows-event-log/wel/WindowsEventLog.h |  48 +++---
 libminifi/include/utils/OsUtils.h                  |   3 +
 libminifi/src/utils/OsUtils.cpp                    |   4 +
 14 files changed, 396 insertions(+), 413 deletions(-)

diff --git a/extensions/windows-event-log/Bookmark.cpp 
b/extensions/windows-event-log/Bookmark.cpp
index aa403d3c0..300aef3c4 100644
--- a/extensions/windows-event-log/Bookmark.cpp
+++ b/extensions/windows-event-log/Bookmark.cpp
@@ -18,7 +18,6 @@
 
 #include "Bookmark.h"
 
-#include <direct.h>
 #include <vector>
 #include <unordered_map>
 #include <utility>
@@ -26,12 +25,9 @@
 
 #include "wel/UnicodeConversion.h"
 #include "utils/file/FileUtils.h"
+#include "utils/OsUtils.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace processors {
+namespace org::apache::nifi::minifi::processors {
 static const std::string BOOKMARK_KEY = "bookmark";
 
 Bookmark::Bookmark(const std::wstring& channel,
@@ -65,7 +61,8 @@ Bookmark::Bookmark(const std::wstring& channel,
   }
 
   if (!bookmarkXml_.empty()) {
-    if (hBookmark_ = unique_evt_handle{ 
EvtCreateBookmark(bookmarkXml_.c_str()) }) {
+    hBookmark_ = unique_evt_handle{EvtCreateBookmark(bookmarkXml_.c_str())};
+    if (hBookmark_) {
       ok_ = true;
       return;
     }
@@ -77,18 +74,19 @@ Bookmark::Bookmark(const std::wstring& channel,
     state_manager_->set(state_map);
   }
 
-  if (!(hBookmark_ = unique_evt_handle{ EvtCreateBookmark(0) })) {
+  hBookmark_ = unique_evt_handle{EvtCreateBookmark(nullptr)};
+  if (!hBookmark_) {
     LOG_LAST_ERROR(EvtCreateBookmark);
     return;
   }
 
-  const auto hEventResults = unique_evt_handle{ EvtQuery(0, channel.c_str(), 
query.c_str(), EvtQueryChannelPath) };
+  const auto hEventResults = unique_evt_handle{ EvtQuery(nullptr, 
channel.c_str(), query.c_str(), EvtQueryChannelPath) };
   if (!hEventResults) {
     LOG_LAST_ERROR(EvtQuery);
     return;
   }
 
-  if (!EvtSeek(hEventResults.get(), 0, 0, 0, processOldEvents? 
EvtSeekRelativeToFirst : EvtSeekRelativeToLast)) {
+  if (!EvtSeek(hEventResults.get(), 0, nullptr, 0, processOldEvents? 
EvtSeekRelativeToFirst : EvtSeekRelativeToLast)) {
     LOG_LAST_ERROR(EvtSeek);
     return;
   }
@@ -129,48 +127,41 @@ bool Bookmark::saveBookmarkXml(const std::wstring& 
bookmarkXml) {
   return state_manager_->set(state_map);
 }
 
-bool Bookmark::saveBookmark(EVT_HANDLE hEvent) {
-  std::wstring bookmarkXml;
-  if (!getNewBookmarkXml(hEvent, bookmarkXml)) {
+bool Bookmark::saveBookmark(EVT_HANDLE event_handle) {
+  auto bookmark_xml = getNewBookmarkXml(event_handle);
+  if (!bookmark_xml) {
+    logger_->log_error("%s", bookmark_xml.error());
     return false;
   }
 
-  return saveBookmarkXml(bookmarkXml);
+  return saveBookmarkXml(*bookmark_xml);
 }
 
-bool Bookmark::getNewBookmarkXml(EVT_HANDLE hEvent, std::wstring& bookmarkXml) 
{
+nonstd::expected<std::wstring, std::string> 
Bookmark::getNewBookmarkXml(EVT_HANDLE hEvent) {
   if (!EvtUpdateBookmark(hBookmark_.get(), hEvent)) {
-    LOG_LAST_ERROR(EvtUpdateBookmark);
-    return false;
+    return nonstd::make_unexpected(fmt::format("EvtUpdateBookmark failed due 
to %s", utils::OsUtils::windowsErrorToErrorCode(GetLastError()).message()));
   }
   // Render the bookmark as an XML string that can be persisted.
   logger_->log_trace("Rendering new bookmark");
   DWORD bufferSize{};
   DWORD bufferUsed{};
   DWORD propertyCount{};
-  if (!EvtRender(nullptr, hBookmark_.get(), EvtRenderBookmark, bufferSize, 
nullptr, &bufferUsed, &propertyCount)) {
-    DWORD status = ERROR_SUCCESS;
-    if (ERROR_INSUFFICIENT_BUFFER == (status = GetLastError())) {
-      bufferSize = bufferUsed;
-
-      std::vector<wchar_t> buf(bufferSize / 2 + 1);
+  bool event_render_succeeded_without_buffer = EvtRender(nullptr, 
hBookmark_.get(), EvtRenderBookmark, bufferSize, nullptr, &bufferUsed, 
&propertyCount);
+  if (event_render_succeeded_without_buffer)
+    return nonstd::make_unexpected("EvtRender failed to determine the required 
buffer size.");
 
-      if (!EvtRender(nullptr, hBookmark_.get(), EvtRenderBookmark, bufferSize, 
&buf[0], &bufferUsed, &propertyCount)) {
-        LOG_LAST_ERROR(EvtRender);
-        return false;
-      }
+  auto last_error = GetLastError();
+  if (last_error != ERROR_INSUFFICIENT_BUFFER)
+    return nonstd::make_unexpected(fmt::format("EvtRender failed due to %s", 
utils::OsUtils::windowsErrorToErrorCode(last_error).message()));
 
-      bookmarkXml = buf.data();
+  bufferSize = bufferUsed;
+  std::vector<wchar_t> buf(bufferSize / 2 + 1);
 
-      return true;
-    }
-    if (ERROR_SUCCESS != (status = GetLastError())) {
-      LOG_LAST_ERROR(EvtRender);
-      return false;
-    }
+  if (!EvtRender(nullptr, hBookmark_.get(), EvtRenderBookmark, bufferSize, 
buf.data(), &bufferUsed, &propertyCount)) {
+    return nonstd::make_unexpected(fmt::format("EvtRender failed due to %s", 
utils::OsUtils::windowsErrorToErrorCode(GetLastError()).message()));
   }
 
-  return false;
+  return std::wstring(buf.data());
 }
 
 bool Bookmark::getBookmarkXmlFromFile(std::wstring& bookmarkXml) {
@@ -212,8 +203,4 @@ bool Bookmark::getBookmarkXmlFromFile(std::wstring& 
bookmarkXml) {
   return true;
 }
 
-} /* namespace processors */
-} /* namespace minifi */
-} /* namespace nifi */
-} /* namespace apache */
-} /* namespace org */
+}  // namespace org::apache::nifi::minifi::processors
diff --git a/extensions/windows-event-log/Bookmark.h 
b/extensions/windows-event-log/Bookmark.h
index 60d6860b8..66937df4f 100644
--- a/extensions/windows-event-log/Bookmark.h
+++ b/extensions/windows-event-log/Bookmark.h
@@ -28,12 +28,9 @@
 #include "core/ProcessSession.h"
 #include "wel/UniqueEvtHandle.h"
 #include "logging/Logger.h"
+#include "utils/expected.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace processors {
+namespace org::apache::nifi::minifi::processors {
 
 #define LOG_LAST_ERROR(func) logger_->log_error("!"#func" error %x", 
GetLastError())
 
@@ -50,7 +47,7 @@ class Bookmark {
   explicit operator bool() const noexcept;
 
   /* non-owning */ EVT_HANDLE getBookmarkHandleFromXML();
-  bool getNewBookmarkXml(EVT_HANDLE hEvent, std::wstring& bookmarkXml);
+  nonstd::expected<std::wstring, std::string> getNewBookmarkXml(EVT_HANDLE 
hEvent);
   bool saveBookmarkXml(const std::wstring& bookmarkXml);
 
  private:
@@ -67,8 +64,4 @@ class Bookmark {
   std::wstring bookmarkXml_;
 };
 
-} /* namespace processors */
-} /* namespace minifi */
-} /* namespace nifi */
-} /* namespace apache */
-} /* namespace org */
+}  // namespace org::apache::nifi::minifi::processors
diff --git a/extensions/windows-event-log/ConsumeWindowsEventLog.cpp 
b/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
index a787470fc..578da4e9f 100644
--- a/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
+++ b/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
@@ -19,7 +19,7 @@
  */
 
 #include "ConsumeWindowsEventLog.h"
-#include <stdio.h>
+#include <cstdio>
 #include <vector>
 #include <tuple>
 #include <utility>
@@ -44,21 +44,19 @@
 #include "core/PropertyBuilder.h"
 #include "core/Resource.h"
 #include "Bookmark.h"
+#include "wel/UniqueEvtHandle.h"
 #include "utils/Deleters.h"
 #include "logging/LoggerConfiguration.h"
 
 #include "utils/gsl.h"
+#include "utils/OsUtils.h"
 
 #pragma comment(lib, "wevtapi.lib")
 #pragma comment(lib, "ole32.lib")
 
 using namespace std::literals::chrono_literals;
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace processors {
+namespace org::apache::nifi::minifi::processors {
 
 const int EVT_NEXT_TIMEOUT_MS = 500;
 
@@ -214,7 +212,7 @@ void ConsumeWindowsEventLog::initialize() {
   setSupportedRelationships(relationships());
 }
 
-bool ConsumeWindowsEventLog::insertHeaderName(wel::METADATA_NAMES &header, 
const std::string &key, const std::string & value) const {
+bool ConsumeWindowsEventLog::insertHeaderName(wel::METADATA_NAMES &header, 
const std::string &key, const std::string & value) {
   wel::METADATA name = 
wel::WindowsEventLogMetadata::getMetadataFromString(key);
 
   if (name != wel::METADATA::UNKNOWN) {
@@ -224,16 +222,15 @@ bool 
ConsumeWindowsEventLog::insertHeaderName(wel::METADATA_NAMES &header, const
   return false;
 }
 
-void ConsumeWindowsEventLog::onSchedule(const 
std::shared_ptr<core::ProcessContext> &context, const 
std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
+void ConsumeWindowsEventLog::onSchedule(const 
std::shared_ptr<core::ProcessContext>& context, const 
std::shared_ptr<core::ProcessSessionFactory>&) {
   state_manager_ = context->getStateManager();
   if (state_manager_ == nullptr) {
     throw Exception(PROCESSOR_EXCEPTION, "Failed to get StateManager");
   }
 
-  context->getProperty(IdentifierMatcher.getName(), regex_);
   context->getProperty(ResolveAsAttributes.getName(), resolve_as_attributes_);
   context->getProperty(IdentifierFunction.getName(), 
apply_identifier_function_);
-  context->getProperty(EventHeaderDelimiter.getName(), header_delimiter_);
+  header_delimiter_ = context->getProperty(EventHeaderDelimiter);
   context->getProperty(BatchCommitSize.getName(), batch_commit_size_);
 
   header_names_.clear();
@@ -256,6 +253,11 @@ void ConsumeWindowsEventLog::onSchedule(const 
std::shared_ptr<core::ProcessConte
     }
   }
 
+  regex_.reset();
+  if (auto identifier_matcher = context->getProperty(IdentifierMatcher); 
identifier_matcher && !identifier_matcher->empty()) {
+    regex_.emplace(*identifier_matcher);
+  }
+
   std::string mode;
   context->getProperty(OutputFormat.getName(), mode);
 
@@ -319,8 +321,8 @@ void ConsumeWindowsEventLog::onSchedule(const 
std::shared_ptr<core::ProcessConte
     }
   }
 
-  context->getProperty(MaxBufferSize.getName(), maxBufferSize_);
-  logger_->log_debug("ConsumeWindowsEventLog: MaxBufferSize %" PRIu64, 
maxBufferSize_);
+  context->getProperty(MaxBufferSize.getName(), max_buffer_size_);
+  logger_->log_debug("ConsumeWindowsEventLog: MaxBufferSize %" PRIu64, 
max_buffer_size_);
 
   provenanceUri_ = "winlog://" + computerName_ + "/" + channel_ + "?" + query;
   logger_->log_trace("Successfully configured CWEL");
@@ -346,7 +348,7 @@ bool ConsumeWindowsEventLog::commitAndSaveBookmark(const 
std::wstring &bookmark_
   return true;
 }
 
-std::tuple<size_t, std::wstring> 
ConsumeWindowsEventLog::processEventLogs(const 
std::shared_ptr<core::ProcessContext> &context,
+std::tuple<size_t, std::wstring> 
ConsumeWindowsEventLog::processEventLogs(const 
std::shared_ptr<core::ProcessContext>&,
     const std::shared_ptr<core::ProcessSession> &session, const EVT_HANDLE& 
event_query_results) {
   size_t processed_event_count = 0;
   std::wstring bookmark_xml;
@@ -367,13 +369,19 @@ std::tuple<size_t, std::wstring> 
ConsumeWindowsEventLog::processEventLogs(const
 
     const auto guard_next_event = gsl::finally([next_event]() { 
EvtClose(next_event); });
     logger_->log_trace("Succesfully got the next event, performing event 
rendering");
-    EventRender event_render;
-    std::wstring new_bookmark_xml;
-    if (createEventRender(next_event, event_render) && 
bookmark_->getNewBookmarkXml(next_event, new_bookmark_xml)) {
-      bookmark_xml = std::move(new_bookmark_xml);
-      processed_event_count++;
-      putEventRenderFlowFileToSession(event_render, *session);
+    auto event_render = createEventRender(next_event);
+    if (!event_render) {
+      logger_->log_error("%s", event_render.error());
+      continue;
     }
+    auto new_bookmark_xml = bookmark_->getNewBookmarkXml(next_event);
+    if (!new_bookmark_xml) {
+      logger_->log_error("%s", new_bookmark_xml.error());
+      continue;
+    }
+    bookmark_xml = std::move(*new_bookmark_xml);
+    processed_event_count++;
+    putEventRenderFlowFileToSession(*event_render, *session);
   }
   logger_->log_trace("Finished enumerating events.");
   return std::make_tuple(processed_event_count, bookmark_xml);
@@ -400,13 +408,12 @@ void ConsumeWindowsEventLog::onTrigger(const 
std::shared_ptr<core::ProcessContex
     logger_->log_debug("processed %zu Events in %"  PRId64 " ms", 
processed_event_count, time_diff());
   });
 
-  const auto event_query_results = EvtQuery(0, wstrChannel_.c_str(), 
wstrQuery_.c_str(), EvtQueryChannelPath);
+  wel::unique_evt_handle event_query_results{EvtQuery(nullptr, 
wstrChannel_.c_str(), wstrQuery_.c_str(), EvtQueryChannelPath)};
   if (!event_query_results) {
     LOG_LAST_ERROR(EvtQuery);
     context->yield();
     return;
   }
-  const auto guard_event_query_results = gsl::finally([event_query_results]() 
{ EvtClose(event_query_results); });
 
   logger_->log_trace("Retrieved results in Channel: %ls with Query: %ls", 
wstrChannel_.c_str(), wstrQuery_.c_str());
 
@@ -418,7 +425,7 @@ void ConsumeWindowsEventLog::onTrigger(const 
std::shared_ptr<core::ProcessContex
     return;
   }
 
-  if (!EvtSeek(event_query_results, 1, bookmark_handle, 0, 
EvtSeekRelativeToBookmark)) {
+  if (!EvtSeek(event_query_results.get(), 1, bookmark_handle, 0, 
EvtSeekRelativeToBookmark)) {
     LOG_LAST_ERROR(EvtSeek);
     context->yield();
     return;
@@ -427,7 +434,7 @@ void ConsumeWindowsEventLog::onTrigger(const 
std::shared_ptr<core::ProcessContex
   refreshTimeZoneData();
 
   std::wstring bookmark_xml;
-  std::tie(processed_event_count, bookmark_xml) = processEventLogs(context, 
session, event_query_results);
+  std::tie(processed_event_count, bookmark_xml) = processEventLogs(context, 
session, event_query_results.get());
 
   if (processed_event_count == 0 || !commitAndSaveBookmark(bookmark_xml, 
context, session)) {
     context->yield();
@@ -446,7 +453,10 @@ wel::WindowsEventLogHandler& 
ConsumeWindowsEventLog::getEventLogHandler(const st
   std::wstring temp_wstring = std::wstring(name.begin(), name.end());
   LPCWSTR widechar = temp_wstring.c_str();
 
-  providers_[name] = 
wel::WindowsEventLogHandler(EvtOpenPublisherMetadata(NULL, widechar, NULL, 0, 
0));
+  auto opened_publisher_metadata_provider = EvtOpenPublisherMetadata(nullptr, 
widechar, nullptr, 0, 0);
+  if (!opened_publisher_metadata_provider)
+    logger_->log_warn("EvtOpenPublisherMetadata failed due to %s", 
utils::OsUtils::windowsErrorToErrorCode(GetLastError()).message());
+  providers_[name] = 
wel::WindowsEventLogHandler(opened_publisher_metadata_provider);
   logger_->log_info("Handler not found for %s, creating. Number of cached 
handlers: %zu", name, providers_.size());
   return providers_[name];
 }
@@ -478,11 +488,11 @@ void 
ConsumeWindowsEventLog::substituteXMLPercentageItems(pugi::xml_document& do
       for (size_t numberPos = 0; std::string::npos != (numberPos = 
nodeText.find(percentages, numberPos));) {
         numberPos += percentages.size();
 
-        auto number = 0u;
+        uint64_t number{};
         try {
           // Assumption - first character is not '0', otherwise not all digits 
will be replaced by 'value'.
           number = std::stoul(&nodeText[numberPos]);
-        } catch (const std::invalid_argument &) {
+        } catch (const std::invalid_argument&) {
           continue;
         }
 
@@ -497,7 +507,7 @@ void 
ConsumeWindowsEventLog::substituteXMLPercentageItems(pugi::xml_document& do
                             number,
                             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                             (LPTSTR)&pBuffer,
-                            1024, 0)) {
+                            1024, nullptr)) {
             value = pBuffer;
             LocalFree(pBuffer);
 
@@ -537,79 +547,82 @@ void 
ConsumeWindowsEventLog::substituteXMLPercentageItems(pugi::xml_document& do
   doc.traverse(treeWalker);
 }
 
-bool ConsumeWindowsEventLog::createEventRender(EVT_HANDLE hEvent, EventRender& 
eventRender) {
+nonstd::expected<std::string, std::string> 
ConsumeWindowsEventLog::renderEventAsXml(EVT_HANDLE event_handle) {
   logger_->log_trace("Rendering an event");
   WCHAR stackBuffer[4096];
   DWORD size = sizeof(stackBuffer);
   using Deleter = utils::StackAwareDeleter<WCHAR, utils::FreeDeleter>;
-  std::unique_ptr<WCHAR, Deleter> buf{stackBuffer, Deleter{ stackBuffer }};
+  std::unique_ptr<WCHAR, Deleter> buf{stackBuffer, Deleter{stackBuffer}};
 
   DWORD used = 0;
   DWORD propertyCount = 0;
-  if (!EvtRender(NULL, hEvent, EvtRenderEventXml, size, buf.get(), &used, 
&propertyCount)) {
-    if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) {
-      LOG_LAST_ERROR(EvtRender);
-      return false;
+  if (!EvtRender(nullptr, event_handle, EvtRenderEventXml, size, buf.get(), 
&used, &propertyCount)) {
+    DWORD last_error = GetLastError();
+    if (ERROR_INSUFFICIENT_BUFFER != last_error) {
+      std::string error_message = fmt::format("EvtRender failed due to %s", 
utils::OsUtils::windowsErrorToErrorCode(last_error).message());
+      return nonstd::make_unexpected(error_message);
     }
-    if (used > maxBufferSize_) {
-      logger_->log_error("Dropping event because it couldn't be rendered 
within %" PRIu64 " bytes.", maxBufferSize_);
-      return false;
+    if (used > max_buffer_size_) {
+      std::string error_message = fmt::format("Dropping event because it 
couldn't be rendered within %" PRIu64 " bytes.", max_buffer_size_);
+      return nonstd::make_unexpected(error_message);
     }
     size = used;
-    buf.reset((LPWSTR)malloc(size));
-    if (!buf) {
-      return false;
-    }
-    if (!EvtRender(NULL, hEvent, EvtRenderEventXml, size, buf.get(), &used, 
&propertyCount)) {
-      LOG_LAST_ERROR(EvtRender);
-      return false;
+    buf.reset((LPWSTR) malloc(size));
+    if (!buf)
+      return nonstd::make_unexpected("malloc failed");
+    if (!EvtRender(nullptr, event_handle, EvtRenderEventXml, size, buf.get(), 
&used, &propertyCount)) {
+      std::string error_message = fmt::format("EvtRender failed due to %s", 
utils::OsUtils::windowsErrorToErrorCode(GetLastError()).message());
+      return nonstd::make_unexpected(error_message);
     }
   }
+  logger_->log_trace("Event rendered with size %" PRIu32, used);
+  return wel::to_string(buf.get());
+}
 
-  logger_->log_debug("Event rendered with size %" PRIu32 ". Performing doc 
traversing...", used);
-
-  std::string xml = wel::to_string(buf.get());
+nonstd::expected<EventRender, std::string> 
ConsumeWindowsEventLog::createEventRender(EVT_HANDLE hEvent) {
+  auto event_as_xml = renderEventAsXml(hEvent);
+  if (!event_as_xml)
+    return nonstd::make_unexpected(event_as_xml.error());
 
   pugi::xml_document doc;
-  pugi::xml_parse_result result = doc.load_string(xml.c_str());
+  if (!doc.load_string(event_as_xml->c_str()))
+    return nonstd::make_unexpected("Invalid XML produced");
 
-  if (!result) {
-    logger_->log_error("Invalid XML produced");
-    return false;
-  }
+  EventRender result;
 
   // this is a well known path.
-  std::string providerName = 
doc.child("Event").child("System").child("Provider").attribute("Name").value();
-  wel::WindowsEventLogMetadataImpl 
metadata{getEventLogHandler(providerName).getMetadata(), hEvent};
-  wel::MetadataWalker walker{metadata, channel_, !resolve_as_attributes_, 
apply_identifier_function_, regex_};
+  std::string provider_name = 
doc.child("Event").child("System").child("Provider").attribute("Name").value();
+  wel::WindowsEventLogMetadataImpl 
metadata{getEventLogHandler(provider_name).getMetadata(), hEvent};
+  wel::MetadataWalker walker{metadata, channel_, !resolve_as_attributes_, 
apply_identifier_function_, regex_ ? &*regex_ : nullptr};
 
   // resolve the event metadata
   doc.traverse(walker);
 
-  logger_->log_debug("Finish doc traversing, performing writing...");
+  logger_->log_trace("Finish doc traversing, performing writing...");
 
   if (output_.plaintext) {
     logger_->log_trace("Writing event in plain text");
 
-    auto& handler = getEventLogHandler(providerName);
-    auto message = handler.getEventMessage(hEvent);
+    auto& handler = getEventLogHandler(provider_name);
+    auto event_message = handler.getEventMessage(hEvent);
 
-    if (!message.empty()) {
-      for (const auto &mapEntry : walker.getIdentifiers()) {
-        // replace the identifiers with their translated strings.
-        if (mapEntry.first.empty() || mapEntry.second.empty()) {
-          continue;  // This is most probably a result of a failed ID 
resolution
+    if (event_message) {
+      for (const auto& map_entry : walker.getIdentifiers()) {
+        if (map_entry.first.empty() || map_entry.second.empty()) {
+          continue;
         }
-        utils::StringUtils::replaceAll(message, mapEntry.first, 
mapEntry.second);
+        utils::StringUtils::replaceAll(*event_message, map_entry.first, 
map_entry.second);
       }
-      wel::WindowsEventLogHeader log_header(header_names_);
-      // set the delimiter
-      log_header.setDelimiter(header_delimiter_);
-      // render the header.
-      eventRender.plaintext = 
log_header.getEventHeader([&walker](wel::METADATA metadata) { return 
walker.getMetadata(metadata); });
-      eventRender.plaintext += "Message" + header_delimiter_ + " ";
-      eventRender.plaintext += message;
     }
+
+    std::string_view payload_name = event_message ? "Message" : "Error";
+
+    wel::WindowsEventLogHeader log_header(header_names_, header_delimiter_, 
payload_name.size());
+    result.plaintext = log_header.getEventHeader([&walker](wel::METADATA 
metadata) { return walker.getMetadata(metadata); });
+    result.plaintext += payload_name;
+    result.plaintext += log_header.getDelimiterFor(payload_name.size());
+    result.plaintext += event_message.has_value() ? *event_message : 
event_message.error().message();
+
     logger_->log_trace("Finish writing in plain text");
   }
 
@@ -618,7 +631,7 @@ bool ConsumeWindowsEventLog::createEventRender(EVT_HANDLE 
hEvent, EventRender& e
     logger_->log_trace("Finish substituting %% in XML");
 
     if (resolve_as_attributes_) {
-      eventRender.matched_fields = walker.getFieldValues();
+      result.matched_fields = walker.getFieldValues();
     }
   }
 
@@ -627,27 +640,27 @@ bool ConsumeWindowsEventLog::createEventRender(EVT_HANDLE 
hEvent, EventRender& e
 
     wel::XmlString writer;
     doc.print(writer, "", pugi::format_raw);  // no indentation or formatting
-    xml = writer.xml_;
+    event_as_xml = writer.xml_;
 
-    eventRender.xml = std::move(xml);
+    result.xml = std::move(*event_as_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));
+    result.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));
+    result.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));
+    result.json = wel::jsonToString(wel::toFlattenedJSON(doc));
     logger_->log_trace("Finish writing in flattened JSON");
   }
 
-  return true;
+  return result;
 }
 
 void ConsumeWindowsEventLog::refreshTimeZoneData() {
@@ -725,18 +738,18 @@ void 
ConsumeWindowsEventLog::putEventRenderFlowFileToSession(const EventRender&
   }
 }
 
-void ConsumeWindowsEventLog::LogWindowsError(std::string error) const {
+void ConsumeWindowsEventLog::LogWindowsError(const std::string& error) const {
   auto error_id = GetLastError();
   LPVOID lpMsg;
 
   FormatMessage(
     FORMAT_MESSAGE_ALLOCATE_BUFFER |
     FORMAT_MESSAGE_FROM_SYSTEM,
-    NULL,
+    nullptr,
     error_id,
     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
     (LPTSTR)&lpMsg,
-    0, NULL);
+    0, nullptr);
 
   logger_->log_error((error + " %x: %s\n").c_str(), 
static_cast<int>(error_id), reinterpret_cast<char *>(lpMsg));
 
@@ -745,8 +758,4 @@ void ConsumeWindowsEventLog::LogWindowsError(std::string 
error) const {
 
 REGISTER_RESOURCE(ConsumeWindowsEventLog, Processor);
 
-}  // namespace processors
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::processors
diff --git a/extensions/windows-event-log/ConsumeWindowsEventLog.h 
b/extensions/windows-event-log/ConsumeWindowsEventLog.h
index 536f0a280..e7530d624 100644
--- a/extensions/windows-event-log/ConsumeWindowsEventLog.h
+++ b/extensions/windows-event-log/ConsumeWindowsEventLog.h
@@ -43,12 +43,9 @@
 #include "concurrentqueue.h"
 #include "pugixml.hpp"
 #include "utils/Export.h"
+#include "utils/RegexUtils.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace processors {
+namespace org::apache::nifi::minifi::processors {
 
 struct EventRender {
   std::map<std::string, std::string> matched_fields;
@@ -63,7 +60,7 @@ class ConsumeWindowsEventLog : public core::Processor {
  public:
   explicit ConsumeWindowsEventLog(const std::string& name, const 
utils::Identifier& uuid = {});
 
-  virtual ~ConsumeWindowsEventLog();
+  ~ConsumeWindowsEventLog() override;
 
   EXTENSIONAPI static constexpr const char* Description = "Windows Event Log 
Subscribe Callback to receive FlowFiles from Events on Windows.";
 
@@ -83,20 +80,20 @@ class ConsumeWindowsEventLog : public core::Processor {
   EXTENSIONAPI static const core::Property ProcessOldEvents;
   static auto properties() {
     return std::array{
-      Channel,
-      Query,
-      MaxBufferSize,
-      InactiveDurationToReconnect,
-      IdentifierMatcher,
-      IdentifierFunction,
-      ResolveAsAttributes,
-      EventHeaderDelimiter,
-      EventHeader,
-      OutputFormat,
-      JSONFormat,
-      BatchCommitSize,
-      BookmarkRootDirectory,
-      ProcessOldEvents
+        Channel,
+        Query,
+        MaxBufferSize,
+        InactiveDurationToReconnect,
+        IdentifierMatcher,
+        IdentifierFunction,
+        ResolveAsAttributes,
+        EventHeaderDelimiter,
+        EventHeader,
+        OutputFormat,
+        JSONFormat,
+        BatchCommitSize,
+        BookmarkRootDirectory,
+        ProcessOldEvents
     };
   }
 
@@ -112,18 +109,20 @@ class ConsumeWindowsEventLog : public core::Processor {
 
   void onSchedule(const std::shared_ptr<core::ProcessContext> &context, const 
std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) override;
   void onTrigger(const std::shared_ptr<core::ProcessContext> &context, const 
std::shared_ptr<core::ProcessSession> &session) override;
-  void initialize(void) override;
+  void initialize() override;
   void notifyStop() override;
 
- protected:
+ private:
   void refreshTimeZoneData();
   void putEventRenderFlowFileToSession(const EventRender& eventRender, 
core::ProcessSession& session) const;
-  wel::WindowsEventLogHandler& getEventLogHandler(const std::string & name);
-  bool insertHeaderName(wel::METADATA_NAMES &header, const std::string &key, 
const std::string &value) const;
-  void LogWindowsError(std::string error = "Error") const;
-  bool createEventRender(EVT_HANDLE eventHandle, EventRender& eventRender);
+  wel::WindowsEventLogHandler& getEventLogHandler(const std::string& name);
+  static bool insertHeaderName(wel::METADATA_NAMES& header, const std::string& 
key, const std::string& value);
+  void LogWindowsError(const std::string& error = "Error") const;
+  nonstd::expected<EventRender, std::string> createEventRender(EVT_HANDLE 
eventHandle);
   void substituteXMLPercentageItems(pugi::xml_document& doc);
 
+  nonstd::expected<std::string, std::string> renderEventAsXml(EVT_HANDLE 
event_handle);
+
   static constexpr const char* XML = "XML";
   static constexpr const char* Both = "Both";
   static constexpr const char* Plaintext = "Plaintext";
@@ -140,29 +139,29 @@ class ConsumeWindowsEventLog : public core::Processor {
     const decltype(std::chrono::steady_clock::now()) time_ = 
std::chrono::steady_clock::now();
   };
 
-  bool commitAndSaveBookmark(const std::wstring &bookmarkXml, const 
std::shared_ptr<core::ProcessContext> &context, const 
std::shared_ptr<core::ProcessSession> &session);
+  bool commitAndSaveBookmark(const std::wstring& bookmarkXml, const 
std::shared_ptr<core::ProcessContext>& context, const 
std::shared_ptr<core::ProcessSession>& session);
 
-  std::tuple<size_t, std::wstring> processEventLogs(const 
std::shared_ptr<core::ProcessContext> &context,
-    const std::shared_ptr<core::ProcessSession> &session, const EVT_HANDLE& 
event_query_results);
+  std::tuple<size_t, std::wstring> processEventLogs(const 
std::shared_ptr<core::ProcessContext>& context,
+                                                    const 
std::shared_ptr<core::ProcessSession>& session,
+                                                    const EVT_HANDLE& 
event_query_results);
 
   std::shared_ptr<core::logging::Logger> logger_;
-  core::CoreComponentStateManager* state_manager_;
+  core::CoreComponentStateManager* state_manager_{nullptr};
   wel::METADATA_NAMES header_names_;
-  std::string header_delimiter_;
+  std::optional<std::string> header_delimiter_;
   std::string channel_;
   std::wstring wstrChannel_;
   std::wstring wstrQuery_;
-  std::string regex_;
+  std::optional<utils::Regex> regex_;
   bool resolve_as_attributes_{false};
   bool apply_identifier_function_{false};
   std::string provenanceUri_;
   std::string computerName_;
-  uint64_t maxBufferSize_{};
-  DWORD lastActivityTimestamp_{};
-  std::map<std::string, wel::WindowsEventLogHandler > providers_;
+  uint64_t max_buffer_size_{};
+  std::map<std::string, wel::WindowsEventLogHandler> providers_;
   uint64_t batch_commit_size_{};
 
-  enum class JSONType {None, Raw, Simple, Flattened};
+  enum class JSONType { None, Raw, Simple, Flattened };
 
   struct OutputFormat {
     bool xml{false};
@@ -185,8 +184,4 @@ class ConsumeWindowsEventLog : public core::Processor {
   std::string timezone_offset_;  // Represented as UTC offset in (+|-)HH:MM 
format, like +02:00
 };
 
-}  // namespace processors
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::processors
diff --git a/extensions/windows-event-log/tests/BookmarkTests.cpp 
b/extensions/windows-event-log/tests/BookmarkTests.cpp
index a05f15988..eeb2c7b1c 100644
--- a/extensions/windows-event-log/tests/BookmarkTests.cpp
+++ b/extensions/windows-event-log/tests/BookmarkTests.cpp
@@ -217,10 +217,10 @@ TEST_CASE("Bookmark::getNewBookmarkXml() updates the 
bookmark", "[add_event]") {
   unique_evt_handle results = queryEvents();
   unique_evt_handle event = getFirstEventFromResults(results);
 
-  std::wstring bookmark_xml_after;
-  REQUIRE(bookmark->getNewBookmarkXml(event.get(), bookmark_xml_after));
+  auto bookmark_xml_after = bookmark->getNewBookmarkXml(event.get());
 
-  REQUIRE(bookmark_xml_before != bookmark_xml_after);
+  REQUIRE(bookmark_xml_after);
+  CHECK(bookmark_xml_before != *bookmark_xml_after);
 }
 
 TEST_CASE("Bookmark::saveBookmarkXml() updates the bookmark and saves it to 
the state manager", "[save_bookmark][state]") {
diff --git a/extensions/windows-event-log/tests/CWELTestUtils.h 
b/extensions/windows-event-log/tests/CWELTestUtils.h
index f1bd1a597..0a0bd85ee 100644
--- a/extensions/windows-event-log/tests/CWELTestUtils.h
+++ b/extensions/windows-event-log/tests/CWELTestUtils.h
@@ -45,7 +45,7 @@ class OutputFormatTestController : public TestController {
       json_format_(std::move(json_format)) {}
 
   std::string run() {
-    LogTestController::getInstance().setDebug<ConsumeWindowsEventLog>();
+    LogTestController::getInstance().setTrace<ConsumeWindowsEventLog>();
     LogTestController::getInstance().setDebug<PutFile>();
     std::shared_ptr<TestPlan> test_plan = createPlan();
 
@@ -69,7 +69,7 @@ class OutputFormatTestController : public TestController {
     }
 
     test_plan->reset();
-    
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+    
LogTestController::resetStream(LogTestController::getInstance().log_output);
 
 
     {
diff --git a/extensions/windows-event-log/tests/ConsumeWindowsEventLogTests.cpp 
b/extensions/windows-event-log/tests/ConsumeWindowsEventLogTests.cpp
index 47d69bc43..c16767ee5 100644
--- a/extensions/windows-event-log/tests/ConsumeWindowsEventLogTests.cpp
+++ b/extensions/windows-event-log/tests/ConsumeWindowsEventLogTests.cpp
@@ -22,19 +22,19 @@
 #include "processors/PutFile.h"
 #include "TestBase.h"
 #include "Catch.h"
-#include "utils/TestUtils.h"
 #include "utils/file/FileUtils.h"
 
-#include "rapidjson/document.h"
-
 #include "CWELTestUtils.h"
 #include "Utils.h"
+#include "../wel/UniqueEvtHandle.h"
+#include "utils/Deleters.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;
+using unique_evt_handle = org::apache::nifi::minifi::wel::unique_evt_handle;
 
 namespace org::apache::nifi::minifi::test {
 
@@ -56,10 +56,10 @@ class SimpleFormatTestController : public 
OutputFormatTestController {
   using OutputFormatTestController::OutputFormatTestController;
 
  protected:
-  void dispatchBookmarkEvent() {
+  void dispatchBookmarkEvent() override {
     reportEvent(APPLICATION_CHANNEL, "Event zero: this is in the past");
   }
-  void OutputFormatTestController::dispatchCollectedEvent() {
+  void dispatchCollectedEvent() override {
     reportEvent(APPLICATION_CHANNEL, "Event one");
   }
 };
@@ -82,7 +82,7 @@ TEST_CASE("ConsumeWindowsEventLog properties work with 
default values", "[create
   std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
 
   auto processor = test_plan->addProcessor("ConsumeWindowsEventLog", "cwel");
-  test_controller.runSession(test_plan);
+  TestController::runSession(test_plan);
 
   auto properties_required_or_with_default_value = {
     ConsumeWindowsEventLog::Channel,
@@ -114,7 +114,7 @@ TEST_CASE("ConsumeWindowsEventLog properties work with 
default values", "[create
     }
   }
 
-  REQUIRE(LogTestController::getInstance().contains("Successfully configured 
CWEL"));
+  CHECK(LogTestController::getInstance().contains("Successfully configured 
CWEL"));
 }
 
 TEST_CASE("ConsumeWindowsEventLog onSchedule throws if it cannot create the 
bookmark", "[create][bookmark]") {
@@ -144,35 +144,37 @@ TEST_CASE("ConsumeWindowsEventLog can consume new 
events", "[onTrigger]") {
 
   reportEvent(APPLICATION_CHANNEL, "Event zero");
 
-  test_controller.runSession(test_plan);
-  REQUIRE(LogTestController::getInstance().contains("processed 0 Events"));
+  TestController::runSession(test_plan);
+  CHECK(LogTestController::getInstance().contains("processed 0 Events"));
   // event zero is not reported as the bookmark is created on the first run
   // and we use the default config setting ProcessOldEvents = false
   // later runs will start with a bookmark saved in the state manager
 
   test_plan->reset();
-  
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+  LogTestController::resetStream(LogTestController::getInstance().log_output);
 
   SECTION("Read one event") {
     reportEvent(APPLICATION_CHANNEL, "Event one");
 
-    test_controller.runSession(test_plan);
-    REQUIRE(LogTestController::getInstance().contains("processed 1 Events"));
-    REQUIRE(LogTestController::getInstance().contains("<EventData><Data>Event 
one</Data></EventData>"));
+    TestController::runSession(test_plan);
+    CHECK(LogTestController::getInstance().contains("processed 1 Events"));
+    CHECK(LogTestController::getInstance().contains("<EventData><Data>Event 
one</Data></EventData>"));
 
     // make sure timezone attributes are present
-    REQUIRE(LogTestController::getInstance().contains("key:timezone.offset 
value:"));
-    REQUIRE(LogTestController::getInstance().contains("key:timezone.name 
value:"));
+    CHECK(LogTestController::getInstance().contains("key:timezone.offset 
value:"));
+    CHECK(LogTestController::getInstance().contains("key:timezone.name 
value:"));
   }
 
-  SECTION("Read two events") {
+  SECTION("Read three events") {
     reportEvent(APPLICATION_CHANNEL, "Event two");
     reportEvent(APPLICATION_CHANNEL, "Event three");
+    reportEvent(APPLICATION_CHANNEL, "%%1844");  // %%1844 expands to System
 
-    test_controller.runSession(test_plan);
-    REQUIRE(LogTestController::getInstance().contains("processed 2 Events"));
-    REQUIRE(LogTestController::getInstance().contains("<EventData><Data>Event 
two</Data></EventData>"));
-    REQUIRE(LogTestController::getInstance().contains("<EventData><Data>Event 
three</Data></EventData>"));
+    TestController::runSession(test_plan);
+    CHECK(LogTestController::getInstance().contains("processed 3 Events"));
+    CHECK(LogTestController::getInstance().contains("<EventData><Data>Event 
two</Data></EventData>"));
+    CHECK(LogTestController::getInstance().contains("<EventData><Data>Event 
three</Data></EventData>"));
+    
CHECK(LogTestController::getInstance().contains("<EventData><Data>System</Data></EventData>"));
   }
 }
 
@@ -191,35 +193,35 @@ TEST_CASE("ConsumeWindowsEventLog bookmarking works", 
"[onTrigger]") {
 
   reportEvent(APPLICATION_CHANNEL, "Event zero");
 
-  test_controller.runSession(test_plan);
-  REQUIRE(LogTestController::getInstance().contains("processed 0 Events"));
+  TestController::runSession(test_plan);
+  CHECK(LogTestController::getInstance().contains("processed 0 Events"));
 
   test_plan->reset();
-  
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+  LogTestController::resetStream(LogTestController::getInstance().log_output);
 
   SECTION("Read in one go") {
     reportEvent(APPLICATION_CHANNEL, "Event one");
     reportEvent(APPLICATION_CHANNEL, "Event two");
     reportEvent(APPLICATION_CHANNEL, "Event three");
 
-    test_controller.runSession(test_plan);
-    REQUIRE(LogTestController::getInstance().contains("processed 3 Events"));
+    TestController::runSession(test_plan);
+    CHECK(LogTestController::getInstance().contains("processed 3 Events"));
   }
 
   SECTION("Read in two batches") {
     reportEvent(APPLICATION_CHANNEL, "Event one");
 
-    test_controller.runSession(test_plan);
-    REQUIRE(LogTestController::getInstance().contains("processed 1 Events"));
+    TestController::runSession(test_plan);
+    CHECK(LogTestController::getInstance().contains("processed 1 Events"));
 
     reportEvent(APPLICATION_CHANNEL, "Event two");
     reportEvent(APPLICATION_CHANNEL, "Event three");
 
     test_plan->reset();
-    
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+    
LogTestController::resetStream(LogTestController::getInstance().log_output);
 
-    test_controller.runSession(test_plan);
-    REQUIRE(LogTestController::getInstance().contains("processed 2 Events"));
+    TestController::runSession(test_plan);
+    CHECK(LogTestController::getInstance().contains("processed 2 Events"));
   }
 }
 
@@ -240,33 +242,33 @@ TEST_CASE("ConsumeWindowsEventLog extracts some 
attributes by default", "[onTrig
   {
     reportEvent(APPLICATION_CHANNEL, "Event zero: this is in the past");
 
-    test_controller.runSession(test_plan);
+    TestController::runSession(test_plan);
   }
 
   test_plan->reset();
-  
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+  LogTestController::resetStream(LogTestController::getInstance().log_output);
 
   // 1st event, on Info level
   {
     reportEvent(APPLICATION_CHANNEL, "Event one: something interesting 
happened", EVENTLOG_INFORMATION_TYPE);
 
-    test_controller.runSession(test_plan);
+    TestController::runSession(test_plan);
 
-    REQUIRE(LogTestController::getInstance().contains("key:Keywords 
value:Classic"));
-    REQUIRE(LogTestController::getInstance().contains("key:Level 
value:Information"));
+    CHECK(LogTestController::getInstance().contains("key:Keywords 
value:Classic"));
+    CHECK(LogTestController::getInstance().contains("key:Level 
value:Information"));
   }
 
   test_plan->reset();
-  
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+  LogTestController::resetStream(LogTestController::getInstance().log_output);
 
   // 2st event, on Warning level
   {
     reportEvent(APPLICATION_CHANNEL, "Event two: something fishy happened!", 
EVENTLOG_WARNING_TYPE);
 
-    test_controller.runSession(test_plan);
+    TestController::runSession(test_plan);
 
-    REQUIRE(LogTestController::getInstance().contains("key:Keywords 
value:Classic"));
-    REQUIRE(LogTestController::getInstance().contains("key:Level 
value:Warning"));
+    CHECK(LogTestController::getInstance().contains("key:Keywords 
value:Classic"));
+    CHECK(LogTestController::getInstance().contains("key:Level 
value:Warning"));
   }
 }
 
@@ -289,18 +291,18 @@ void outputFormatSetterTestHelper(const std::string 
&output_format, int expected
   {
     reportEvent(APPLICATION_CHANNEL, "Event zero: this is in the past");
 
-    test_controller.runSession(test_plan);
+    TestController::runSession(test_plan);
   }
 
   test_plan->reset();
-  
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+  LogTestController::resetStream(LogTestController::getInstance().log_output);
 
   {
     reportEvent(APPLICATION_CHANNEL, "Event one");
 
-    test_controller.runSession(test_plan);
+    TestController::runSession(test_plan);
 
-    REQUIRE(LogTestController::getInstance().contains("Logged " + 
std::to_string(expected_num_flow_files) + " flow files"));
+    CHECK(LogTestController::getInstance().contains("Logged " + 
std::to_string(expected_num_flow_files) + " flow files"));
   }
 }
 
@@ -316,31 +318,44 @@ TEST_CASE("ConsumeWindowsEventLog output format can be 
set", "[create][output_fo
   outputFormatSetterTestHelper("InvalidValue", 0);
 }
 
-// NOTE(fgerlits): I don't know how to unit test this, as my manually 
published events all result in an empty string if OutputFormat is Plaintext
-//                 but it does seem to work, based on manual tests reading 
system logs
-// TEST_CASE("ConsumeWindowsEventLog prints events in plain text correctly", 
"[onTrigger]")
+TEST_CASE("ConsumeWindowsEventLog prints events in plain text correctly", 
"[onTrigger]") {
+  std::string event = SimpleFormatTestController{APPLICATION_CHANNEL, QUERY, 
"Plaintext"}.run();
+  CHECK(!event.empty());
+  CHECK(event.find(R"(Log Name:      Application)") != std::string::npos);
+  CHECK(event.find(R"(Source:        Application)") != std::string::npos);
+  CHECK(event.find(R"(Date:          )") != std::string::npos);
+  CHECK(event.find(R"(Record ID:     )") != std::string::npos);
+  CHECK(event.find(R"(Event ID:      14985)") != std::string::npos);
+  CHECK(event.find(R"(Task Category: N/A)") != std::string::npos);
+  CHECK(event.find(R"(Level:         Information)") != std::string::npos);
+  CHECK(event.find(R"(Keywords:      Classic)") != std::string::npos);
+  CHECK(event.find(R"(User:          N/A)") != std::string::npos);
+  CHECK(event.find(R"(Computer:      )") != std::string::npos);
+  CHECK(event.find(R"(EventType:     4)") != std::string::npos);
+  CHECK(event.find(R"(Error:         The message resource is present but the 
message was not found in the message table.)") != std::string::npos);
+}
 
 TEST_CASE("ConsumeWindowsEventLog prints events in XML correctly", 
"[onTrigger]") {
   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);
+  CHECK(event.find(R"(<Event 
xmlns="http://schemas.microsoft.com/win/2004/08/events/event";><System><Provider 
Name="Application"/>)") != std::string::npos);
+  CHECK(event.find(R"(<EventID Qualifiers="0">14985</EventID>)") != 
std::string::npos);
+  CHECK(event.find(R"(<Level>4</Level>)") != std::string::npos);
+  CHECK(event.find(R"(<Task>0</Task>)") != std::string::npos);
+  CHECK(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);
+  CHECK(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);
+  CHECK(event.find(R"(</EventRecordID>)") != std::string::npos);
+  CHECK(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);
+  CHECK(event.find(R"(</Computer><Security/></System><EventData><Data>Event 
one</Data></EventData></Event>)") != std::string::npos);
 }
 
 TEST_CASE("ConsumeWindowsEventLog prints events in JSON::Simple correctly", 
"[onTrigger]") {
   std::string event = SimpleFormatTestController{APPLICATION_CHANNEL, "*", 
"JSON", "Simple"}.run();
   // the json must be single-line
-  REQUIRE(event.find('\n') == std::string::npos);
+  CHECK(event.find('\n') == std::string::npos);
   utils::verifyJSON(event, R"json(
     {
       "System": {
@@ -410,11 +425,11 @@ void batchCommitSizeTestHelper(std::size_t 
num_events_read, std::size_t batch_co
   {
     reportEvent(APPLICATION_CHANNEL, "Event zero: this is in the past");
 
-    test_controller.runSession(test_plan);
+    TestController::runSession(test_plan);
   }
 
   test_plan->reset();
-  
LogTestController::getInstance().resetStream(LogTestController::getInstance().log_output);
+  LogTestController::resetStream(LogTestController::getInstance().log_output);
 
   auto generate_events = [](const std::size_t event_count) {
     std::vector<std::string> events;
@@ -427,8 +442,8 @@ void batchCommitSizeTestHelper(std::size_t num_events_read, 
std::size_t batch_co
   for (const auto& event : generate_events(num_events_read))
     reportEvent(APPLICATION_CHANNEL, event.c_str());
 
-  test_controller.runSession(test_plan);
-  REQUIRE(LogTestController::getInstance().contains("processed " + 
std::to_string(expected_event_count) + " Events"));
+  TestController::runSession(test_plan);
+  CHECK(LogTestController::getInstance().contains("processed " + 
std::to_string(expected_event_count) + " Events"));
 }
 
 }  // namespace
diff --git a/extensions/windows-event-log/tests/MetadataWalkerTests.cpp 
b/extensions/windows-event-log/tests/MetadataWalkerTests.cpp
index c84fba61f..ee0339cfe 100644
--- a/extensions/windows-event-log/tests/MetadataWalkerTests.cpp
+++ b/extensions/windows-event-log/tests/MetadataWalkerTests.cpp
@@ -16,12 +16,8 @@
  * limitations under the License.
  */
 
-#include <fstream>
 #include <map>
-#include <memory>
-#include <utility>
 #include <string>
-#include <set>
 
 #include "TestBase.h"
 #include "Catch.h"
@@ -32,12 +28,29 @@
 
 using METADATA = org::apache::nifi::minifi::wel::METADATA;
 using MetadataWalker = org::apache::nifi::minifi::wel::MetadataWalker;
-using WindowsEventLogHandler = 
org::apache::nifi::minifi::wel::WindowsEventLogHandler;
 using WindowsEventLogMetadata = 
org::apache::nifi::minifi::wel::WindowsEventLogMetadata;
+using WindowsEventLogMetadataImpl = 
org::apache::nifi::minifi::wel::WindowsEventLogMetadataImpl;
 using XmlString = org::apache::nifi::minifi::wel::XmlString;
 
 namespace {
 
+std::string updateXmlMetadata(const std::string &xml, EVT_HANDLE metadata_ptr, 
EVT_HANDLE event_ptr, bool update_xml, bool resolve, utils::Regex const* regex 
= nullptr) {
+  WindowsEventLogMetadataImpl metadata{metadata_ptr, event_ptr};
+  MetadataWalker walker(metadata, "", update_xml, resolve, regex);
+
+  pugi::xml_document doc;
+  pugi::xml_parse_result result = doc.load_string(xml.c_str());
+
+  if (result) {
+    doc.traverse(walker);
+    XmlString writer;
+    doc.print(writer, "", pugi::format_raw);  // no indentation or formatting
+    return writer.xml_;
+  } else {
+    throw std::runtime_error("Could not parse XML document");
+  }
+}
+
 std::string formatXml(const std::string &xml) {
   pugi::xml_document doc;
   pugi::xml_parse_result result = doc.load_string(xml.c_str());
@@ -60,8 +73,8 @@ const short event_type_index = 178;  // NOLINT short comes 
from WINDOWS API
 
 class FakeWindowsEventLogMetadata : public WindowsEventLogMetadata {
  public:
-  std::string getEventData(EVT_FORMAT_MESSAGE_FLAGS flags) const override { 
return "event_data_for_flag_" + std::to_string(flags); }
-  std::string getEventTimestamp() const override { return "event_timestamp"; }
+  [[nodiscard]] std::string getEventData(EVT_FORMAT_MESSAGE_FLAGS flags) const 
override { return "event_data_for_flag_" + std::to_string(flags); }
+  [[nodiscard]] std::string getEventTimestamp() const override { return 
"event_timestamp"; }
   short getEventTypeIndex() const override { return event_type_index; }  // 
NOLINT short comes from WINDOWS API
 };
 
@@ -71,33 +84,35 @@ TEST_CASE("MetadataWalker updates the Sid in the XML if 
both update_xml and reso
   std::string xml = readFile("resources/nobodysid.xml");
 
   SECTION("No resolution") {
-    REQUIRE(MetadataWalker::updateXmlMetadata(xml, 0x00, 0x00, false, true) == 
formatXml(xml));
+    REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, false, true) == 
formatXml(xml));
   }
 
   SECTION("Resolve nobody") {
     std::string nobody = readFile("resources/withsids.xml");
-    REQUIRE(MetadataWalker::updateXmlMetadata(xml, 0x00, 0x00, true, true, 
".*Sid") == formatXml(nobody));
+    auto regex = utils::Regex(".*Sid");
+    REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, true, true, &regex) == 
formatXml(nobody));
   }
 }
 
 TEST_CASE("MetadataWalker works even when there is no Data block", 
"[updateXmlMetadata]") {
   std::string xml = readFile("resources/nodata.xml");
 
-  REQUIRE(MetadataWalker::updateXmlMetadata(xml, 0x00, 0x00, false, true) == 
formatXml(xml));
+  REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, false, true) == 
formatXml(xml));
 }
 
 TEST_CASE("MetadataWalker throws if the input XML is invalid", 
"[updateXmlMetadata]") {
   std::string xml = readFile("resources/invalidxml.xml");
 
-  REQUIRE_THROWS(MetadataWalker::updateXmlMetadata(xml, 0x00, 0x00, false, 
true) == formatXml(xml));
+  REQUIRE_THROWS(updateXmlMetadata(xml, nullptr, nullptr, false, true) == 
formatXml(xml));
 }
 
 TEST_CASE("MetadataWalker will leave a Sid unchanged if it doesn't correspond 
to a user", "[updateXmlMetadata]") {
   std::string xml = readFile("resources/unknownsid.xml");
 
-  REQUIRE(MetadataWalker::updateXmlMetadata(xml, 0x00, 0x00, false, true) == 
formatXml(xml));
-  REQUIRE(MetadataWalker::updateXmlMetadata(xml, 0x00, 0x00, true, true) == 
formatXml(xml));
-  REQUIRE(MetadataWalker::updateXmlMetadata(xml, 0x00, 0x00, true, true, 
".*Sid") == formatXml(xml));
+  REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, false, true) == 
formatXml(xml));
+  REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, true, true) == 
formatXml(xml));
+  auto regex = utils::Regex(".*Sid");
+  REQUIRE(updateXmlMetadata(xml, nullptr, nullptr, true, true, &regex) == 
formatXml(xml));
 }
 
 TEST_CASE("MetadataWalker can replace multiple Sids", "[updateXmlMetadata]") {
@@ -106,7 +121,7 @@ TEST_CASE("MetadataWalker can replace multiple Sids", 
"[updateXmlMetadata]") {
   std::string programmaticallyResolved;
 
   pugi::xml_document doc;
-  xml = MetadataWalker::updateXmlMetadata(xml, 0x00, 0x00, false, true);
+  xml = updateXmlMetadata(xml, nullptr, nullptr, false, true);
   pugi::xml_parse_result result = doc.load_string(xml.c_str());
 
   for (const auto &node : doc.child("Event").child("EventData").children()) {
@@ -128,21 +143,23 @@ namespace {
 void extractMappingsTestHelper(const std::string &file_name,
                                bool update_xml,
                                bool resolve,
-                               std::map<std::string, std::string> 
expected_identifiers,
-                               std::map<METADATA, std::string> 
expected_metadata,
-                               std::map<std::string, std::string> 
expected_field_values) {
+                               const std::map<std::string, std::string>& 
expected_identifiers,
+                               const std::map<METADATA, std::string>& 
expected_metadata,
+                               const std::map<std::string, std::string>& 
expected_field_values) {
   std::string input_xml = readFile(file_name);
+  REQUIRE(!input_xml.empty());
   pugi::xml_document doc;
   pugi::xml_parse_result result = doc.load_string(input_xml.c_str());
-  REQUIRE(result);
+  CHECK(result);
 
-  MetadataWalker walker(FakeWindowsEventLogMetadata{}, 
METADATA_WALKER_TESTS_LOG_NAME, update_xml, resolve, ".*Sid");
+  auto regex = utils::Regex(".*Sid");
+  MetadataWalker walker(FakeWindowsEventLogMetadata{}, 
METADATA_WALKER_TESTS_LOG_NAME, update_xml, resolve, &regex);
   doc.traverse(walker);
 
-  REQUIRE(walker.getIdentifiers() == expected_identifiers);
-  REQUIRE(walker.getFieldValues() == expected_field_values);
+  CHECK(walker.getIdentifiers() == expected_identifiers);
+  CHECK(walker.getFieldValues() == expected_field_values);
   for (const auto &key_value_pair : expected_metadata) {
-    REQUIRE(walker.getMetadata(key_value_pair.first) == key_value_pair.second);
+    CHECK(walker.getMetadata(key_value_pair.first) == key_value_pair.second);
   }
 }
 
diff --git a/extensions/windows-event-log/wel/MetadataWalker.cpp 
b/extensions/windows-event-log/wel/MetadataWalker.cpp
index bf7eb6784..8b5ca7c5e 100644
--- a/extensions/windows-event-log/wel/MetadataWalker.cpp
+++ b/extensions/windows-event-log/wel/MetadataWalker.cpp
@@ -30,11 +30,7 @@
 #include "MetadataWalker.h"
 #include "XMLString.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace wel {
+namespace org::apache::nifi::minifi::wel {
 
 bool MetadataWalker::for_each(pugi::xml_node &node) {
   // don't shortcut resolution here so that we can log attributes.
@@ -43,7 +39,7 @@ bool MetadataWalker::for_each(pugi::xml_node &node) {
     for (pugi::xml_attribute attr : node.attributes())  {
       const auto idUpdate = [&](const std::string &input) {
         if (resolve_) {
-          const auto resolved = utils::OsUtils::userIdToUsername(input);
+          auto resolved = utils::OsUtils::userIdToUsername(input);
           replaced_identifiers_[input] = resolved;
           return resolved;
         }
@@ -106,11 +102,11 @@ bool MetadataWalker::for_each(pugi::xml_node &node) {
   return true;
 }
 
-std::vector<std::string> MetadataWalker::getIdentifiers(const std::string 
&text) const {
+std::vector<std::string> MetadataWalker::getIdentifiers(const std::string 
&text) {
   auto pos = text.find("%{");
   std::vector<std::string> found_strings;
   while (pos != std::string::npos) {
-    auto next_pos = text.find("}", pos);
+    auto next_pos = text.find('}', pos);
     if (next_pos != std::string::npos) {
       auto potential_identifier = text.substr(pos + 2, next_pos - (pos + 2));
       std::smatch match;
@@ -146,7 +142,7 @@ std::string MetadataWalker::getMetadata(METADATA metadata) 
const {
       case EVENT_TYPE:
         return std::to_string(windows_event_log_metadata_.getEventTypeIndex());
       case COMPUTER:
-        return windows_event_log_metadata_.getComputerName();
+        return WindowsEventLogMetadata::getComputerName();
     }
     return "N/A";
 }
@@ -159,23 +155,6 @@ std::map<std::string, std::string> 
MetadataWalker::getIdentifiers() const {
   return replaced_identifiers_;
 }
 
-std::string MetadataWalker::updateXmlMetadata(const std::string &xml, 
EVT_HANDLE metadata_ptr, EVT_HANDLE event_ptr, bool update_xml, bool resolve, 
const std::string &regex) {
-  WindowsEventLogMetadataImpl metadata{metadata_ptr, event_ptr};
-  MetadataWalker walker(metadata, "", update_xml, resolve, regex);
-
-  pugi::xml_document doc;
-  pugi::xml_parse_result result = doc.load_string(xml.c_str());
-
-  if (result) {
-    doc.traverse(walker);
-    wel::XmlString writer;
-    doc.print(writer, "", pugi::format_raw);  // no indentation or formatting
-    return writer.xml_;
-  } else {
-    throw std::runtime_error("Could not parse XML document");
-  }
-}
-
 std::string MetadataWalker::to_string(const wchar_t* pChar) {
   return std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(pChar);
 }
@@ -193,8 +172,4 @@ void MetadataWalker::updateText(pugi::xml_node &node, const 
std::string &field_n
   }
 }
 
-} /* namespace wel */
-} /* namespace minifi */
-} /* namespace nifi */
-} /* namespace apache */
-} /* namespace org */
+}  // namespace org::apache::nifi::minifi::wel
diff --git a/extensions/windows-event-log/wel/MetadataWalker.h 
b/extensions/windows-event-log/wel/MetadataWalker.h
index 05fc2ea61..014dadbdb 100644
--- a/extensions/windows-event-log/wel/MetadataWalker.h
+++ b/extensions/windows-event-log/wel/MetadataWalker.h
@@ -29,6 +29,7 @@
 #include <string>
 #include <vector>
 #include <optional>
+#include <utility>
 
 #include "core/Core.h"
 #include "core/Processor.h"
@@ -41,11 +42,7 @@
 #include "pugixml.hpp"
 #include "utils/RegexUtils.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace wel {
+namespace org::apache::nifi::minifi::wel {
 
 /**
  * Defines a tree walker for the XML input
@@ -53,10 +50,10 @@ namespace wel {
  */
 class MetadataWalker : public pugi::xml_tree_walker {
  public:
-  MetadataWalker(const WindowsEventLogMetadata& windows_event_log_metadata, 
const std::string &log_name, bool update_xml, bool resolve, const std::string 
&regex = "")
+  MetadataWalker(const WindowsEventLogMetadata& windows_event_log_metadata, 
std::string log_name, bool update_xml, bool resolve, utils::Regex const* regex)
       : windows_event_log_metadata_(windows_event_log_metadata),
-        log_name_(log_name),
-        regex_(regex.empty() ? std::nullopt : std::make_optional(regex)),
+        log_name_(std::move(log_name)),
+        regex_(regex),
         update_xml_(update_xml),
         resolve_(resolve) {
   }
@@ -67,16 +64,14 @@ class MetadataWalker : public pugi::xml_tree_walker {
    */
   bool for_each(pugi::xml_node &node) override;
 
-  static std::string updateXmlMetadata(const std::string &xml, EVT_HANDLE 
metadata_ptr, EVT_HANDLE event_ptr, bool update_xml, bool resolve, const 
std::string &regex = "");
+  [[nodiscard]] std::map<std::string, std::string> getFieldValues() const;
 
-  std::map<std::string, std::string> getFieldValues() const;
+  [[nodiscard]] std::map<std::string, std::string> getIdentifiers() const;
 
-  std::map<std::string, std::string> getIdentifiers() const;
-
-  std::string getMetadata(METADATA metadata) const;
+  [[nodiscard]] std::string getMetadata(METADATA metadata) const;
 
  private:
-  std::vector<std::string> getIdentifiers(const std::string &text) const;
+  static std::vector<std::string> getIdentifiers(const std::string &text);
 
   static std::string getString(const std::map<std::string, std::string> &map, 
const std::string &field) {
     auto srch = map.find(field);
@@ -94,17 +89,13 @@ class MetadataWalker : public pugi::xml_tree_walker {
   void updateText(pugi::xml_node &node, const std::string &field_name, 
std::function<std::string(const std::string &)> &&fn);
 
   const WindowsEventLogMetadata& windows_event_log_metadata_;
-  std::string log_name_;
-  std::optional<utils::Regex> regex_;
-  bool update_xml_;
-  bool resolve_;
+  const std::string log_name_;
+  utils::Regex const * const regex_;
+  const bool update_xml_;
+  const bool resolve_;
   std::map<std::string, std::string> metadata_;
   std::map<std::string, std::string> fields_values_;
   std::map<std::string, std::string> replaced_identifiers_;
 };
 
-} /* namespace wel */
-} /* namespace minifi */
-} /* namespace nifi */
-} /* namespace apache */
-} /* namespace org */
+}  // namespace org::apache::nifi::minifi::wel
diff --git a/extensions/windows-event-log/wel/WindowsEventLog.cpp 
b/extensions/windows-event-log/wel/WindowsEventLog.cpp
index 1fa624872..ce10b91d0 100644
--- a/extensions/windows-event-log/wel/WindowsEventLog.cpp
+++ b/extensions/windows-event-log/wel/WindowsEventLog.cpp
@@ -18,6 +18,7 @@
 #include <winmeta.h>
 
 #include <algorithm>
+#include <chrono>
 #include <memory>
 #include <string>
 
@@ -25,64 +26,53 @@
 #include "UnicodeConversion.h"
 #include "utils/Deleters.h"
 #include "utils/gsl.h"
+#include "UniqueEvtHandle.h"
 
 namespace org::apache::nifi::minifi::wel {
 
+namespace {
+std::string getEventTimestampStr(uint64_t event_timestamp) {
+  constexpr std::chrono::duration<int64_t> nt_to_unix_epoch{-11644473600};  // 
January 1, 1601 (NT epoch) - January 1, 1970 (Unix epoch):
+
+  const std::chrono::duration<int64_t, std::ratio<1, 10'000'000>> 
event_timestamp_as_duration{event_timestamp};
+  const auto converted_timestamp = 
std::chrono::system_clock::time_point{event_timestamp_as_duration + 
nt_to_unix_epoch};
+
+  return date::format("%m/%d/%Y %r %p", 
std::chrono::floor<std::chrono::milliseconds>(converted_timestamp));
+}
+}  // namespace
+
 void WindowsEventLogMetadataImpl::renderMetadata() {
   DWORD status = ERROR_SUCCESS;
   EVT_VARIANT stackBuffer[4096];
   DWORD dwBufferSize = sizeof(stackBuffer);
   using Deleter = utils::StackAwareDeleter<EVT_VARIANT, utils::FreeDeleter>;
-  std::unique_ptr<EVT_VARIANT, Deleter> rendered_values{ stackBuffer, 
Deleter{stackBuffer} };
+  std::unique_ptr<EVT_VARIANT, Deleter> rendered_values{stackBuffer, 
Deleter{stackBuffer}};
   DWORD dwBufferUsed = 0;
   DWORD dwPropertyCount = 0;
 
-  auto context = EvtCreateRenderContext(0, NULL, EvtRenderContextSystem);
-  if (context == NULL) {
+  unique_evt_handle context{EvtCreateRenderContext(0, nullptr, 
EvtRenderContextSystem)};
+  if (!context)
     return;
-  }
-  const auto contextGuard = gsl::finally([&context](){
-    EvtClose(context);
-  });
-  if (!EvtRender(context, event_ptr_, EvtRenderEventValues, dwBufferSize, 
rendered_values.get(), &dwBufferUsed, &dwPropertyCount)) {
+
+  if (!EvtRender(context.get(), event_ptr_, EvtRenderEventValues, 
dwBufferSize, rendered_values.get(), &dwBufferUsed, &dwPropertyCount)) {
     if (ERROR_INSUFFICIENT_BUFFER != (status = GetLastError())) {
       return;
     }
 
     dwBufferSize = dwBufferUsed;
-    rendered_values.reset((PEVT_VARIANT)(malloc(dwBufferSize)));
+    rendered_values.reset((PEVT_VARIANT) (malloc(dwBufferSize)));
     if (!rendered_values) {
       return;
     }
 
-    EvtRender(context, event_ptr_, EvtRenderEventValues, dwBufferSize, 
rendered_values.get(), &dwBufferUsed, &dwPropertyCount);
+    EvtRender(context.get(), event_ptr_, EvtRenderEventValues, dwBufferSize, 
rendered_values.get(), &dwBufferUsed, &dwPropertyCount);
     if (ERROR_SUCCESS != (status = GetLastError())) {
       return;
     }
   }
 
-  event_timestamp_ = 
static_cast<PEVT_VARIANT>(rendered_values.get())[EvtSystemTimeCreated].FileTimeVal;
-
-  SYSTEMTIME st;
-  FILETIME ft;
-
-  ft.dwHighDateTime = (DWORD)((event_timestamp_ >> 32) & 0xFFFFFFFF);
-  ft.dwLowDateTime = (DWORD)(event_timestamp_ & 0xFFFFFFFF);
-
-  FileTimeToSystemTime(&ft, &st);
-  std::stringstream datestr;
+  event_timestamp_str_ = 
getEventTimestampStr(static_cast<PEVT_VARIANT>(rendered_values.get())[EvtSystemTimeCreated].FileTimeVal);
 
-  std::string period = "AM";
-  auto hour = st.wHour;
-  if (hour >= 12 && hour < 24)
-    period = "PM";
-  if (hour > 12)
-    hour -= 12;
-  if (hour == 0)
-    hour = 12;
-  datestr << st.wMonth << "/" << st.wDay << "/" << st.wYear << " " << 
std::setfill('0') << std::setw(2) << hour << ":" << std::setfill('0')
-          << std::setw(2) << st.wMinute << ":" << std::setfill('0') << 
std::setw(2) << st.wSecond << " " << period;
-  event_timestamp_str_ = datestr.str();
   auto level = 
static_cast<PEVT_VARIANT>(rendered_values.get())[EvtSystemLevel];
   auto keyword = 
static_cast<PEVT_VARIANT>(rendered_values.get())[EvtSystemKeywords];
   if (level.Type == EvtVarTypeByte) {
@@ -121,20 +111,18 @@ std::string 
WindowsEventLogMetadataImpl::getEventData(EVT_FORMAT_MESSAGE_FLAGS f
   WCHAR stack_buffer[4096];
   DWORD num_chars_in_buffer = sizeof(stack_buffer) / sizeof(stack_buffer[0]);
   using Deleter = utils::StackAwareDeleter<WCHAR, utils::FreeDeleter>;
-  std::unique_ptr<WCHAR, Deleter> buffer{ stack_buffer, Deleter{stack_buffer} 
};
+  std::unique_ptr<WCHAR, Deleter> buffer{stack_buffer, Deleter{stack_buffer}};
   DWORD num_chars_used = 0;
-  DWORD result = 0;
 
   std::string event_data;
 
-  if (metadata_ptr_ == NULL || event_ptr_ == NULL) {
+  if (!metadata_ptr_ || !event_ptr_) {
     return event_data;
   }
 
-
-  if (!EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, NULL, flags, 
num_chars_in_buffer, buffer.get(), &num_chars_used)) {
-    result = GetLastError();
-    if (ERROR_INSUFFICIENT_BUFFER == result) {
+  if (!EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, nullptr, flags, 
num_chars_in_buffer, buffer.get(), &num_chars_used)) {
+    auto last_error = GetLastError();
+    if (ERROR_INSUFFICIENT_BUFFER == last_error) {
       num_chars_in_buffer = num_chars_used;
 
       buffer.reset((LPWSTR) malloc(num_chars_in_buffer * sizeof(WCHAR)));
@@ -142,7 +130,7 @@ std::string 
WindowsEventLogMetadataImpl::getEventData(EVT_FORMAT_MESSAGE_FLAGS f
         return event_data;
       }
 
-      EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, NULL, flags, 
num_chars_in_buffer, buffer.get(), &num_chars_used);
+      EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, nullptr, flags, 
num_chars_in_buffer, buffer.get(), &num_chars_used);
     }
   }
 
@@ -158,51 +146,59 @@ std::string 
WindowsEventLogMetadataImpl::getEventData(EVT_FORMAT_MESSAGE_FLAGS f
   return event_data;
 }
 
-std::string WindowsEventLogHandler::getEventMessage(EVT_HANDLE eventHandle) 
const {
+nonstd::expected<std::string, std::error_code> 
WindowsEventLogHandler::getEventMessage(EVT_HANDLE eventHandle) const {
   std::string returnValue;
   WCHAR stack_buffer[4096];
   DWORD num_chars_in_buffer = sizeof(stack_buffer) / sizeof(stack_buffer[0]);
   using Deleter = utils::StackAwareDeleter<WCHAR, utils::FreeDeleter>;
-  std::unique_ptr<WCHAR, Deleter> buffer{ stack_buffer, Deleter{stack_buffer} 
};
+  std::unique_ptr<WCHAR, Deleter> buffer{stack_buffer, Deleter{stack_buffer}};
   DWORD num_chars_used = 0;
-  DWORD status = 0;
 
-  EvtFormatMessage(metadata_provider_.get(), eventHandle, 0, 0, NULL, 
EvtFormatMessageEvent, num_chars_in_buffer, buffer.get(), &num_chars_used);
-  if (num_chars_used == 0) {
-    return returnValue;
-  }
+  bool evt_format_succeeded = EvtFormatMessage(metadata_provider_.get(), 
eventHandle, 0, 0, nullptr, EvtFormatMessageEvent, num_chars_in_buffer, 
buffer.get(), &num_chars_used);
+  if (evt_format_succeeded)
+    return to_string(buffer.get());
 
-  //  we need to get the size of the buffer
-  status = GetLastError();
-  if (ERROR_INSUFFICIENT_BUFFER == status) {
-    num_chars_in_buffer = num_chars_used;
-
-    /* All C++ examples use malloc and even HeapAlloc in some cases. To avoid 
any problems ( with EvtFormatMessage calling
-      free for example ) we will continue to use malloc and use a custom 
deleter with unique_ptr.
-    '*/
-    buffer.reset((LPWSTR)malloc(num_chars_in_buffer * sizeof(WCHAR)));
-    if (!buffer) {
-      return returnValue;
-    }
+  DWORD status = GetLastError();
 
-    EvtFormatMessage(metadata_provider_.get(), eventHandle, 0, 0, NULL, 
EvtFormatMessageEvent, num_chars_in_buffer, buffer.get(), &num_chars_used);
-  }
+  if (status != ERROR_INSUFFICIENT_BUFFER)
+    return 
nonstd::make_unexpected(utils::OsUtils::windowsErrorToErrorCode(status));
+
+  num_chars_in_buffer = num_chars_used;
+  buffer.reset((LPWSTR) malloc(num_chars_in_buffer * sizeof(WCHAR)));
+  if (!buffer)
+    return 
nonstd::make_unexpected(utils::OsUtils::windowsErrorToErrorCode(ERROR_OUTOFMEMORY));
+  if (EvtFormatMessage(metadata_provider_.get(), eventHandle, 0, 0, nullptr,
+                       EvtFormatMessageEvent, num_chars_in_buffer,
+                       buffer.get(), &num_chars_used))
+    return to_string(buffer.get());
+  return 
nonstd::make_unexpected(utils::OsUtils::windowsErrorToErrorCode(GetLastError()));
+}
 
-  if (ERROR_EVT_MESSAGE_NOT_FOUND == status || ERROR_EVT_MESSAGE_ID_NOT_FOUND 
== status) {
-    return returnValue;
+namespace {
+size_t findLongestHeaderNameSize(const METADATA_NAMES& header_names, const 
size_t minimum_size) {
+  size_t max = minimum_size;
+  for (const auto& option : header_names) {
+    max = (std::max(max, option.second.size()));
   }
+  return ++max;
+}
+}  // namespace
 
-  // convert wstring to std::string
-  return to_string(buffer.get());
+WindowsEventLogHeader::WindowsEventLogHeader(const METADATA_NAMES& 
header_names, const std::optional<std::string>& custom_delimiter, const size_t 
minimum_size)
+    : header_names_(header_names),
+      custom_delimiter_(custom_delimiter),
+      longest_header_name_(findLongestHeaderNameSize(header_names, 
minimum_size)) {
 }
 
-void WindowsEventLogHeader::setDelimiter(const std::string &delim) {
-  delimiter_ = delim;
+std::string WindowsEventLogHeader::getDelimiterFor(size_t length) const {
+  if (custom_delimiter_)
+    return *custom_delimiter_;
+  return createDefaultDelimiter(length);
 }
 
-std::string WindowsEventLogHeader::createDefaultDelimiter(size_t max, size_t 
length) const {
-  if (max > length) {
-    return ":" + std::string(max - length, ' ');
+std::string WindowsEventLogHeader::createDefaultDelimiter(size_t length) const 
{
+  if (longest_header_name_ > length) {
+    return ":" + std::string(longest_header_name_ - length, ' ');
   } else {
     return ": ";
   }
diff --git a/extensions/windows-event-log/wel/WindowsEventLog.h 
b/extensions/windows-event-log/wel/WindowsEventLog.h
index 4eb44c5b3..e12ecbe9a 100644
--- a/extensions/windows-event-log/wel/WindowsEventLog.h
+++ b/extensions/windows-event-log/wel/WindowsEventLog.h
@@ -39,6 +39,7 @@
 #include "UniqueEvtHandle.h"
 
 #include "pugixml.hpp"
+#include "utils/expected.h"
 
 namespace org::apache::nifi::minifi::wel {
 
@@ -59,9 +60,9 @@ enum METADATA {
 };
 
 
-// this is a continuous enum so we can rely on the array
+// this is a continuous enum, so we can rely on the array
 
-typedef std::vector<std::pair<METADATA, std::string>> METADATA_NAMES;
+using METADATA_NAMES = std::vector<std::pair<METADATA, std::string>>;
 
 class WindowsEventLogHandler {
  public:
@@ -71,9 +72,9 @@ class WindowsEventLogHandler {
   explicit WindowsEventLogHandler(EVT_HANDLE metadataProvider) : 
metadata_provider_(metadataProvider) {
   }
 
-  std::string getEventMessage(EVT_HANDLE eventHandle) const;
+  nonstd::expected<std::string, std::error_code> getEventMessage(EVT_HANDLE 
eventHandle) const;
 
-  EVT_HANDLE getMetadata() const;
+  [[nodiscard]] EVT_HANDLE getMetadata() const;
 
  private:
   unique_evt_handle metadata_provider_;
@@ -82,8 +83,8 @@ class WindowsEventLogHandler {
 class WindowsEventLogMetadata {
  public:
   virtual ~WindowsEventLogMetadata() = default;
-  virtual std::string getEventData(EVT_FORMAT_MESSAGE_FLAGS flags) const = 0;
-  virtual std::string getEventTimestamp() const = 0;
+  [[nodiscard]] virtual std::string getEventData(EVT_FORMAT_MESSAGE_FLAGS 
flags) const = 0;
+  [[nodiscard]] virtual std::string getEventTimestamp() const = 0;
   virtual short getEventTypeIndex() const = 0;  // NOLINT short comes from 
WINDOWS API
 
   static std::string getMetadataString(METADATA val) {
@@ -146,22 +147,21 @@ class WindowsEventLogMetadata {
 
 class WindowsEventLogMetadataImpl : public WindowsEventLogMetadata {
  public:
-  WindowsEventLogMetadataImpl(EVT_HANDLE metadataProvider, EVT_HANDLE 
event_ptr) : metadata_ptr_(metadataProvider), event_timestamp_(0), 
event_ptr_(event_ptr) {
+  WindowsEventLogMetadataImpl(EVT_HANDLE metadataProvider, EVT_HANDLE 
event_ptr) : metadata_ptr_(metadataProvider), event_ptr_(event_ptr) {
     renderMetadata();
   }
 
-  std::string getEventData(EVT_FORMAT_MESSAGE_FLAGS flags) const override;
+  [[nodiscard]] std::string getEventData(EVT_FORMAT_MESSAGE_FLAGS flags) const 
override;
 
-  std::string getEventTimestamp() const override { return 
event_timestamp_str_; }
+  [[nodiscard]] std::string getEventTimestamp() const override { return 
event_timestamp_str_; }
 
   short getEventTypeIndex() const override { return event_type_index_; }  // 
NOLINT short comes from WINDOWS API
 
  private:
   void renderMetadata();
 
-  uint64_t event_timestamp_;
   std::string event_type_;
-  short event_type_index_;  // NOLINT short comes from WINDOWS API
+  short event_type_index_ = 0;  // NOLINT short comes from WINDOWS API
   std::string event_timestamp_str_;
   EVT_HANDLE event_ptr_;
   EVT_HANDLE metadata_ptr_;
@@ -169,33 +169,31 @@ class WindowsEventLogMetadataImpl : public 
WindowsEventLogMetadata {
 
 class WindowsEventLogHeader {
  public:
-  explicit WindowsEventLogHeader(METADATA_NAMES header_names) : 
header_names_(header_names) {}
-
-  void setDelimiter(const std::string& delim);
+  explicit WindowsEventLogHeader(const METADATA_NAMES& header_names, const 
std::optional<std::string>& custom_delimiter, size_t minimum_size);
 
   template<typename MetadataCollection>
   std::string getEventHeader(const MetadataCollection& metadata_collection) 
const;
 
+  [[nodiscard]] std::string getDelimiterFor(size_t length) const;
  private:
-  std::string createDefaultDelimiter(size_t max, size_t length) const;
+  [[nodiscard]] std::string createDefaultDelimiter(size_t length) const;
 
-  std::string delimiter_;
-  METADATA_NAMES header_names_;
+  const METADATA_NAMES& header_names_;
+  const std::optional<std::string>& custom_delimiter_;
+  const size_t longest_header_name_;
 };
 
 template<typename MetadataCollection>
 std::string WindowsEventLogHeader::getEventHeader(const MetadataCollection& 
metadata_collection) const {
   std::stringstream eventHeader;
-  size_t max = 1;
-  for (const auto& option : header_names_) {
-    max = (std::max(max, option.second.size()));
-  }
-  ++max;  // increment by one to get space.
+
   for (const auto& option : header_names_) {
     auto name = option.second;
-    if (!name.empty()) {
-      eventHeader << name << (delimiter_.empty() ? createDefaultDelimiter(max, 
name.size()) : delimiter_);
-    }
+    eventHeader << name;
+    if (custom_delimiter_)
+      eventHeader << *custom_delimiter_;
+    else
+      eventHeader << createDefaultDelimiter(name.size());
     eventHeader << utils::StringUtils::trim(metadata_collection(option.first)) 
<< std::endl;
   }
 
diff --git a/libminifi/include/utils/OsUtils.h 
b/libminifi/include/utils/OsUtils.h
index 66c9ea85c..a6ef83d58 100644
--- a/libminifi/include/utils/OsUtils.h
+++ b/libminifi/include/utils/OsUtils.h
@@ -18,6 +18,7 @@
 
 #include <string>
 #include <optional>
+#include <system_error>
 
 struct sockaddr;
 
@@ -38,6 +39,8 @@ int64_t getSystemTotalPhysicalMemory();
 #ifdef WIN32
 /// Returns the total paging file size in bytes
 int64_t getTotalPagingFileSize();
+
+std::error_code windowsErrorToErrorCode(unsigned long error_code);  // NOLINT 
[runtime/int]
 #endif
 
 /// Returns the host architecture (e.g. x32, arm64)
diff --git a/libminifi/src/utils/OsUtils.cpp b/libminifi/src/utils/OsUtils.cpp
index 219cfe2b9..708ce426f 100644
--- a/libminifi/src/utils/OsUtils.cpp
+++ b/libminifi/src/utils/OsUtils.cpp
@@ -284,6 +284,10 @@ int64_t OsUtils::getTotalPagingFileSize() {
   DWORDLONG total_paging_file_size = memory_info.ullTotalPageFile;
   return total_paging_file_size;
 }
+
+std::error_code OsUtils::windowsErrorToErrorCode(DWORD error_code) {
+  return {gsl::narrow_cast<int>(error_code), std::system_category()};
+}
 #endif
 
 std::string OsUtils::getMachineArchitecture() {

Reply via email to