Break out Journal EventLog

As part of a EventLog refactor, break out all code
used for Systems Journal EventLog, in order to make the code base
more maintainable in the long run.

Tested: Only code extraction. Code compiles.

Change-Id: I855c8c680d3de68944b0ae03ff7f181e6790c456
Signed-off-by: Oliver Brewka <oliver.brewka@9elements.com>
diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
index e1f3fb6..ecb8272 100644
--- a/redfish-core/lib/log_services.hpp
+++ b/redfish-core/lib/log_services.hpp
@@ -19,7 +19,6 @@
 #include "human_sort.hpp"
 #include "logging.hpp"
 #include "query.hpp"
-#include "registries.hpp"
 #include "registries/privilege_registry.hpp"
 #include "str_utility.hpp"
 #include "task.hpp"
@@ -28,7 +27,6 @@
 #include "utils/dbus_utils.hpp"
 #include "utils/json_utils.hpp"
 #include "utils/log_services_utils.hpp"
-#include "utils/query_param.hpp"
 #include "utils/time_utils.hpp"
 
 #include <asm-generic/errno.h>
@@ -49,24 +47,17 @@
 #include <algorithm>
 #include <array>
 #include <chrono>
-#include <cstddef>
 #include <cstdint>
-#include <cstdio>
-#include <ctime>
 #include <filesystem>
 #include <format>
-#include <fstream>
 #include <functional>
-#include <iomanip>
 #include <iterator>
 #include <memory>
 #include <optional>
 #include <ranges>
 #include <span>
-#include <sstream>
 #include <string>
 #include <string_view>
-#include <system_error>
 #include <utility>
 #include <variant>
 #include <vector>
@@ -137,70 +128,6 @@
     return dbusDumpPath;
 }
 
-inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
-                             const bool firstEntry = true)
-{
-    static time_t prevTs = 0;
-    static int index = 0;
-    if (firstEntry)
-    {
-        prevTs = 0;
-    }
-
-    // Get the entry timestamp
-    std::time_t curTs = 0;
-    std::tm timeStruct = {};
-    std::istringstream entryStream(logEntry);
-    if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
-    {
-        curTs = std::mktime(&timeStruct);
-    }
-    // If the timestamp isn't unique, increment the index
-    if (curTs == prevTs)
-    {
-        index++;
-    }
-    else
-    {
-        // Otherwise, reset it
-        index = 0;
-    }
-    // Save the timestamp
-    prevTs = curTs;
-
-    entryID = std::to_string(curTs);
-    if (index > 0)
-    {
-        entryID += "_" + std::to_string(index);
-    }
-    return true;
-}
-
-inline bool getRedfishLogFiles(
-    std::vector<std::filesystem::path>& redfishLogFiles)
-{
-    static const std::filesystem::path redfishLogDir = "/var/log";
-    static const std::string redfishLogFilename = "redfish";
-
-    // Loop through the directory looking for redfish log files
-    for (const std::filesystem::directory_entry& dirEnt :
-         std::filesystem::directory_iterator(redfishLogDir))
-    {
-        // If we find a redfish log file, save the path
-        std::string filename = dirEnt.path().filename();
-        if (filename.starts_with(redfishLogFilename))
-        {
-            redfishLogFiles.emplace_back(redfishLogDir / filename);
-        }
-    }
-    // As the log files rotate, they are appended with a ".#" that is higher for
-    // the older logs. Since we don't expect more than 10 log files, we
-    // can just sort the list to get them in order from newest to oldest
-    std::ranges::sort(redfishLogFiles);
-
-    return !redfishLogFiles.empty();
-}
-
 inline log_entry::OriginatorTypes mapDbusOriginatorTypeToRedfish(
     const std::string& originatorType)
 {
@@ -1208,146 +1135,6 @@
         });
 }
 
-inline void handleSystemsLogServicesEventLogActionsClearPost(
-    App& app, const crow::Request& req,
-    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-    const std::string& systemName)
-{
-    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-    {
-        return;
-    }
-    if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
-    {
-        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                   systemName);
-        return;
-    }
-
-    // Clear the EventLog by deleting the log files
-    std::vector<std::filesystem::path> redfishLogFiles;
-    if (getRedfishLogFiles(redfishLogFiles))
-    {
-        for (const std::filesystem::path& file : redfishLogFiles)
-        {
-            std::error_code ec;
-            std::filesystem::remove(file, ec);
-        }
-    }
-
-    // Reload rsyslog so it knows to start new log files
-    dbus::utility::async_method_call(
-        asyncResp,
-        [asyncResp](const boost::system::error_code& ec) {
-            if (ec)
-            {
-                BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec);
-                messages::internalError(asyncResp->res);
-                return;
-            }
-
-            messages::success(asyncResp->res);
-        },
-        "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
-        "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
-        "replace");
-}
-
-inline void requestRoutesJournalEventLogClear(App& app)
-{
-    BMCWEB_ROUTE(
-        app,
-        "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
-        .privileges(redfish::privileges::
-                        postLogServiceSubOverComputerSystemLogServiceCollection)
-        .methods(boost::beast::http::verb::post)(std::bind_front(
-            handleSystemsLogServicesEventLogActionsClearPost, std::ref(app)));
-}
-
-enum class LogParseError
-{
-    success,
-    parseFailed,
-    messageIdNotInRegistry,
-};
-
-static LogParseError fillEventLogEntryJson(
-    const std::string& logEntryID, const std::string& logEntry,
-    nlohmann::json::object_t& logEntryJson)
-{
-    // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
-    // First get the Timestamp
-    size_t space = logEntry.find_first_of(' ');
-    if (space == std::string::npos)
-    {
-        return LogParseError::parseFailed;
-    }
-    std::string timestamp = logEntry.substr(0, space);
-    // Then get the log contents
-    size_t entryStart = logEntry.find_first_not_of(' ', space);
-    if (entryStart == std::string::npos)
-    {
-        return LogParseError::parseFailed;
-    }
-    std::string_view entry(logEntry);
-    entry.remove_prefix(entryStart);
-    // Use split to separate the entry into its fields
-    std::vector<std::string> logEntryFields;
-    bmcweb::split(logEntryFields, entry, ',');
-    // We need at least a MessageId to be valid
-    auto logEntryIter = logEntryFields.begin();
-    if (logEntryIter == logEntryFields.end())
-    {
-        return LogParseError::parseFailed;
-    }
-    std::string& messageID = *logEntryIter;
-    // Get the Message from the MessageRegistry
-    const registries::Message* message = registries::getMessage(messageID);
-
-    logEntryIter++;
-    if (message == nullptr)
-    {
-        BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry);
-        return LogParseError::messageIdNotInRegistry;
-    }
-
-    std::vector<std::string_view> messageArgs(logEntryIter,
-                                              logEntryFields.end());
-    messageArgs.resize(message->numberOfArgs);
-
-    std::string msg =
-        redfish::registries::fillMessageArgs(messageArgs, message->message);
-    if (msg.empty())
-    {
-        return LogParseError::parseFailed;
-    }
-
-    // Get the Created time from the timestamp. The log timestamp is in RFC3339
-    // format which matches the Redfish format except for the fractional seconds
-    // between the '.' and the '+', so just remove them.
-    std::size_t dot = timestamp.find_first_of('.');
-    std::size_t plus = timestamp.find_first_of('+');
-    if (dot != std::string::npos && plus != std::string::npos)
-    {
-        timestamp.erase(dot, plus - dot);
-    }
-
-    // Fill in the log entry with the gathered data
-    logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
-    logEntryJson["@odata.id"] = boost::urls::format(
-        "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
-        BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
-    logEntryJson["Name"] = "System Event Log Entry";
-    logEntryJson["Id"] = logEntryID;
-    logEntryJson["Message"] = std::move(msg);
-    logEntryJson["MessageId"] = std::move(messageID);
-    logEntryJson["MessageArgs"] = messageArgs;
-    logEntryJson["EntryType"] = "Event";
-    logEntryJson["Severity"] = message->messageSeverity;
-    logEntryJson["Created"] = std::move(timestamp);
-    return LogParseError::success;
-}
-
 inline void fillEventLogLogEntryFromDbusLogEntry(
     const DbusEventLogEntry& entry, nlohmann::json& objectToFillOut)
 {
@@ -1435,202 +1222,6 @@
     asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
 }
 
-inline void handleSystemsLogServiceEventLogLogEntryCollection(
-    App& app, const crow::Request& req,
-    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-    const std::string& systemName)
-{
-    query_param::QueryCapabilities capabilities = {
-        .canDelegateTop = true,
-        .canDelegateSkip = true,
-    };
-    query_param::Query delegatedQuery;
-    if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
-                                                  delegatedQuery, capabilities))
-    {
-        return;
-    }
-    if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
-    {
-        // Option currently returns no systems.  TBD
-        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                   systemName);
-        return;
-    }
-    if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
-    {
-        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                   systemName);
-        return;
-    }
-
-    size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
-    size_t skip = delegatedQuery.skip.value_or(0);
-
-    // Collections don't include the static data added by SubRoute
-    // because it has a duplicate entry for members
-    asyncResp->res.jsonValue["@odata.type"] =
-        "#LogEntryCollection.LogEntryCollection";
-    asyncResp->res.jsonValue["@odata.id"] =
-        std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
-                    BMCWEB_REDFISH_SYSTEM_URI_NAME);
-    asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
-    asyncResp->res.jsonValue["Description"] =
-        "Collection of System Event Log Entries";
-
-    nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
-    logEntryArray = nlohmann::json::array();
-    // Go through the log files and create a unique ID for each
-    // entry
-    std::vector<std::filesystem::path> redfishLogFiles;
-    getRedfishLogFiles(redfishLogFiles);
-    uint64_t entryCount = 0;
-    std::string logEntry;
-
-    // Oldest logs are in the last file, so start there and loop
-    // backwards
-    for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
-    {
-        std::ifstream logStream(*it);
-        if (!logStream.is_open())
-        {
-            continue;
-        }
-
-        // Reset the unique ID on the first entry
-        bool firstEntry = true;
-        while (std::getline(logStream, logEntry))
-        {
-            std::string idStr;
-            if (!getUniqueEntryID(logEntry, idStr, firstEntry))
-            {
-                continue;
-            }
-            firstEntry = false;
-
-            nlohmann::json::object_t bmcLogEntry;
-            LogParseError status =
-                fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
-            if (status == LogParseError::messageIdNotInRegistry)
-            {
-                continue;
-            }
-            if (status != LogParseError::success)
-            {
-                messages::internalError(asyncResp->res);
-                return;
-            }
-
-            entryCount++;
-            // Handle paging using skip (number of entries to skip from the
-            // start) and top (number of entries to display)
-            if (entryCount <= skip || entryCount > skip + top)
-            {
-                continue;
-            }
-
-            logEntryArray.emplace_back(std::move(bmcLogEntry));
-        }
-    }
-    asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
-    if (skip + top < entryCount)
-    {
-        asyncResp->res.jsonValue["Members@odata.nextLink"] =
-            boost::urls::format(
-                "/redfish/v1/Systems/{}/LogServices/EventLog/Entries?$skip={}",
-                BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(skip + top));
-    }
-}
-
-inline void requestRoutesJournalEventLogEntryCollection(App& app)
-{
-    BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
-        .privileges(redfish::privileges::getLogEntryCollection)
-        .methods(boost::beast::http::verb::get)(std::bind_front(
-            handleSystemsLogServiceEventLogLogEntryCollection, std::ref(app)));
-}
-
-inline void handleSystemsLogServiceEventLogEntriesGet(
-    App& app, const crow::Request& req,
-    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-    const std::string& systemName, const std::string& param)
-{
-    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-    {
-        return;
-    }
-    if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
-    {
-        // Option currently returns no systems.  TBD
-        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                   systemName);
-        return;
-    }
-
-    if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
-    {
-        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                   systemName);
-        return;
-    }
-
-    const std::string& targetID = param;
-
-    // Go through the log files and check the unique ID for each
-    // entry to find the target entry
-    std::vector<std::filesystem::path> redfishLogFiles;
-    getRedfishLogFiles(redfishLogFiles);
-    std::string logEntry;
-
-    // Oldest logs are in the last file, so start there and loop
-    // backwards
-    for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
-    {
-        std::ifstream logStream(*it);
-        if (!logStream.is_open())
-        {
-            continue;
-        }
-
-        // Reset the unique ID on the first entry
-        bool firstEntry = true;
-        while (std::getline(logStream, logEntry))
-        {
-            std::string idStr;
-            if (!getUniqueEntryID(logEntry, idStr, firstEntry))
-            {
-                continue;
-            }
-            firstEntry = false;
-
-            if (idStr == targetID)
-            {
-                nlohmann::json::object_t bmcLogEntry;
-                LogParseError status =
-                    fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
-                if (status != LogParseError::success)
-                {
-                    messages::internalError(asyncResp->res);
-                    return;
-                }
-                asyncResp->res.jsonValue.update(bmcLogEntry);
-                return;
-            }
-        }
-    }
-    // Requested ID was not found
-    messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
-}
-
-inline void requestRoutesJournalEventLogEntry(App& app)
-{
-    BMCWEB_ROUTE(
-        app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
-        .privileges(redfish::privileges::getLogEntry)
-        .methods(boost::beast::http::verb::get)(std::bind_front(
-            handleSystemsLogServiceEventLogEntriesGet, std::ref(app)));
-}
-
 inline void dBusEventLogEntryCollection(
     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
 {
diff --git a/redfish-core/lib/systems_logservices_journal_eventlog.hpp b/redfish-core/lib/systems_logservices_journal_eventlog.hpp
new file mode 100644
index 0000000..9c56a86
--- /dev/null
+++ b/redfish-core/lib/systems_logservices_journal_eventlog.hpp
@@ -0,0 +1,457 @@
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+// SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
+#pragma once
+
+#include "bmcweb_config.h"
+
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "dbus_utility.hpp"
+#include "error_messages.hpp"
+#include "http_request.hpp"
+#include "http_response.hpp"
+#include "logging.hpp"
+#include "query.hpp"
+#include "registries.hpp"
+#include "registries/privilege_registry.hpp"
+#include "str_utility.hpp"
+#include "utils/query_param.hpp"
+
+#include <asm-generic/errno.h>
+#include <systemd/sd-bus.h>
+#include <unistd.h>
+
+#include <boost/beast/http/field.hpp>
+#include <boost/beast/http/status.hpp>
+#include <boost/beast/http/verb.hpp>
+#include <boost/system/linux_error.hpp>
+#include <boost/url/format.hpp>
+#include <boost/url/url.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/message/native_types.hpp>
+#include <sdbusplus/unpack_properties.hpp>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <ctime>
+#include <filesystem>
+#include <format>
+#include <fstream>
+#include <functional>
+#include <iomanip>
+#include <iterator>
+#include <memory>
+#include <optional>
+#include <span>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+namespace redfish
+{
+
+inline bool getRedfishLogFiles(
+    std::vector<std::filesystem::path>& redfishLogFiles)
+{
+    static const std::filesystem::path redfishLogDir = "/var/log";
+    static const std::string redfishLogFilename = "redfish";
+
+    // Loop through the directory looking for redfish log files
+    for (const std::filesystem::directory_entry& dirEnt :
+         std::filesystem::directory_iterator(redfishLogDir))
+    {
+        // If we find a redfish log file, save the path
+        std::string filename = dirEnt.path().filename();
+        if (filename.starts_with(redfishLogFilename))
+        {
+            redfishLogFiles.emplace_back(redfishLogDir / filename);
+        }
+    }
+    // As the log files rotate, they are appended with a ".#" that is higher for
+    // the older logs. Since we don't expect more than 10 log files, we
+    // can just sort the list to get them in order from newest to oldest
+    std::ranges::sort(redfishLogFiles);
+
+    return !redfishLogFiles.empty();
+}
+
+inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
+                             const bool firstEntry = true)
+{
+    static time_t prevTs = 0;
+    static int index = 0;
+    if (firstEntry)
+    {
+        prevTs = 0;
+    }
+
+    // Get the entry timestamp
+    std::time_t curTs = 0;
+    std::tm timeStruct = {};
+    std::istringstream entryStream(logEntry);
+    if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
+    {
+        curTs = std::mktime(&timeStruct);
+    }
+    // If the timestamp isn't unique, increment the index
+    if (curTs == prevTs)
+    {
+        index++;
+    }
+    else
+    {
+        // Otherwise, reset it
+        index = 0;
+    }
+    // Save the timestamp
+    prevTs = curTs;
+
+    entryID = std::to_string(curTs);
+    if (index > 0)
+    {
+        entryID += "_" + std::to_string(index);
+    }
+    return true;
+}
+
+enum class LogParseError
+{
+    success,
+    parseFailed,
+    messageIdNotInRegistry,
+};
+
+static LogParseError fillEventLogEntryJson(
+    const std::string& logEntryID, const std::string& logEntry,
+    nlohmann::json::object_t& logEntryJson)
+{
+    // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
+    // First get the Timestamp
+    size_t space = logEntry.find_first_of(' ');
+    if (space == std::string::npos)
+    {
+        return LogParseError::parseFailed;
+    }
+    std::string timestamp = logEntry.substr(0, space);
+    // Then get the log contents
+    size_t entryStart = logEntry.find_first_not_of(' ', space);
+    if (entryStart == std::string::npos)
+    {
+        return LogParseError::parseFailed;
+    }
+    std::string_view entry(logEntry);
+    entry.remove_prefix(entryStart);
+    // Use split to separate the entry into its fields
+    std::vector<std::string> logEntryFields;
+    bmcweb::split(logEntryFields, entry, ',');
+    // We need at least a MessageId to be valid
+    auto logEntryIter = logEntryFields.begin();
+    if (logEntryIter == logEntryFields.end())
+    {
+        return LogParseError::parseFailed;
+    }
+    std::string& messageID = *logEntryIter;
+    // Get the Message from the MessageRegistry
+    const registries::Message* message = registries::getMessage(messageID);
+
+    logEntryIter++;
+    if (message == nullptr)
+    {
+        BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry);
+        return LogParseError::messageIdNotInRegistry;
+    }
+
+    std::vector<std::string_view> messageArgs(logEntryIter,
+                                              logEntryFields.end());
+    messageArgs.resize(message->numberOfArgs);
+
+    std::string msg =
+        redfish::registries::fillMessageArgs(messageArgs, message->message);
+    if (msg.empty())
+    {
+        return LogParseError::parseFailed;
+    }
+
+    // Get the Created time from the timestamp. The log timestamp is in RFC3339
+    // format which matches the Redfish format except for the fractional seconds
+    // between the '.' and the '+', so just remove them.
+    std::size_t dot = timestamp.find_first_of('.');
+    std::size_t plus = timestamp.find_first_of('+');
+    if (dot != std::string::npos && plus != std::string::npos)
+    {
+        timestamp.erase(dot, plus - dot);
+    }
+
+    // Fill in the log entry with the gathered data
+    logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
+    logEntryJson["@odata.id"] = boost::urls::format(
+        "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
+        BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
+    logEntryJson["Name"] = "System Event Log Entry";
+    logEntryJson["Id"] = logEntryID;
+    logEntryJson["Message"] = std::move(msg);
+    logEntryJson["MessageId"] = std::move(messageID);
+    logEntryJson["MessageArgs"] = messageArgs;
+    logEntryJson["EntryType"] = "Event";
+    logEntryJson["Severity"] = message->messageSeverity;
+    logEntryJson["Created"] = std::move(timestamp);
+    return LogParseError::success;
+}
+
+inline void handleSystemsLogServiceEventLogLogEntryCollection(
+    App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& systemName)
+{
+    query_param::QueryCapabilities capabilities = {
+        .canDelegateTop = true,
+        .canDelegateSkip = true,
+    };
+    query_param::Query delegatedQuery;
+    if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
+                                                  delegatedQuery, capabilities))
+    {
+        return;
+    }
+    if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
+    {
+        // Option currently returns no systems.  TBD
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+    if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+    {
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
+    size_t skip = delegatedQuery.skip.value_or(0);
+
+    // Collections don't include the static data added by SubRoute
+    // because it has a duplicate entry for members
+    asyncResp->res.jsonValue["@odata.type"] =
+        "#LogEntryCollection.LogEntryCollection";
+    asyncResp->res.jsonValue["@odata.id"] =
+        std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
+                    BMCWEB_REDFISH_SYSTEM_URI_NAME);
+    asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
+    asyncResp->res.jsonValue["Description"] =
+        "Collection of System Event Log Entries";
+
+    nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
+    logEntryArray = nlohmann::json::array();
+    // Go through the log files and create a unique ID for each
+    // entry
+    std::vector<std::filesystem::path> redfishLogFiles;
+    getRedfishLogFiles(redfishLogFiles);
+    uint64_t entryCount = 0;
+    std::string logEntry;
+
+    // Oldest logs are in the last file, so start there and loop
+    // backwards
+    for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
+    {
+        std::ifstream logStream(*it);
+        if (!logStream.is_open())
+        {
+            continue;
+        }
+
+        // Reset the unique ID on the first entry
+        bool firstEntry = true;
+        while (std::getline(logStream, logEntry))
+        {
+            std::string idStr;
+            if (!getUniqueEntryID(logEntry, idStr, firstEntry))
+            {
+                continue;
+            }
+            firstEntry = false;
+
+            nlohmann::json::object_t bmcLogEntry;
+            LogParseError status =
+                fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
+            if (status == LogParseError::messageIdNotInRegistry)
+            {
+                continue;
+            }
+            if (status != LogParseError::success)
+            {
+                messages::internalError(asyncResp->res);
+                return;
+            }
+
+            entryCount++;
+            // Handle paging using skip (number of entries to skip from the
+            // start) and top (number of entries to display)
+            if (entryCount <= skip || entryCount > skip + top)
+            {
+                continue;
+            }
+
+            logEntryArray.emplace_back(std::move(bmcLogEntry));
+        }
+    }
+    asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
+    if (skip + top < entryCount)
+    {
+        asyncResp->res.jsonValue["Members@odata.nextLink"] =
+            boost::urls::format(
+                "/redfish/v1/Systems/{}/LogServices/EventLog/Entries?$skip={}",
+                BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(skip + top));
+    }
+}
+
+inline void handleSystemsLogServiceEventLogEntriesGet(
+    App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& systemName, const std::string& param)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+    if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
+    {
+        // Option currently returns no systems.  TBD
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+    {
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    const std::string& targetID = param;
+
+    // Go through the log files and check the unique ID for each
+    // entry to find the target entry
+    std::vector<std::filesystem::path> redfishLogFiles;
+    getRedfishLogFiles(redfishLogFiles);
+    std::string logEntry;
+
+    // Oldest logs are in the last file, so start there and loop
+    // backwards
+    for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
+    {
+        std::ifstream logStream(*it);
+        if (!logStream.is_open())
+        {
+            continue;
+        }
+
+        // Reset the unique ID on the first entry
+        bool firstEntry = true;
+        while (std::getline(logStream, logEntry))
+        {
+            std::string idStr;
+            if (!getUniqueEntryID(logEntry, idStr, firstEntry))
+            {
+                continue;
+            }
+            firstEntry = false;
+
+            if (idStr == targetID)
+            {
+                nlohmann::json::object_t bmcLogEntry;
+                LogParseError status =
+                    fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
+                if (status != LogParseError::success)
+                {
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+                asyncResp->res.jsonValue.update(bmcLogEntry);
+                return;
+            }
+        }
+    }
+    // Requested ID was not found
+    messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
+}
+
+inline void handleSystemsLogServicesEventLogActionsClearPost(
+    App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& systemName)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+    if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+    {
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    // Clear the EventLog by deleting the log files
+    std::vector<std::filesystem::path> redfishLogFiles;
+    if (getRedfishLogFiles(redfishLogFiles))
+    {
+        for (const std::filesystem::path& file : redfishLogFiles)
+        {
+            std::error_code ec;
+            std::filesystem::remove(file, ec);
+        }
+    }
+
+    // Reload rsyslog so it knows to start new log files
+    dbus::utility::async_method_call(
+        asyncResp,
+        [asyncResp](const boost::system::error_code& ec) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec);
+                messages::internalError(asyncResp->res);
+                return;
+            }
+
+            messages::success(asyncResp->res);
+        },
+        "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
+        "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
+        "replace");
+}
+
+inline void requestRoutesJournalEventLogEntryCollection(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
+        .privileges(redfish::privileges::getLogEntryCollection)
+        .methods(boost::beast::http::verb::get)(std::bind_front(
+            handleSystemsLogServiceEventLogLogEntryCollection, std::ref(app)));
+}
+
+inline void requestRoutesJournalEventLogEntry(App& app)
+{
+    BMCWEB_ROUTE(
+        app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
+        .privileges(redfish::privileges::getLogEntry)
+        .methods(boost::beast::http::verb::get)(std::bind_front(
+            handleSystemsLogServiceEventLogEntriesGet, std::ref(app)));
+}
+
+inline void requestRoutesJournalEventLogClear(App& app)
+{
+    BMCWEB_ROUTE(
+        app,
+        "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
+        .privileges(redfish::privileges::
+                        postLogServiceSubOverComputerSystemLogServiceCollection)
+        .methods(boost::beast::http::verb::post)(std::bind_front(
+            handleSystemsLogServicesEventLogActionsClearPost, std::ref(app)));
+}
+} // namespace redfish
diff --git a/redfish-core/src/redfish.cpp b/redfish-core/src/redfish.cpp
index c797251..f189770 100644
--- a/redfish-core/src/redfish.cpp
+++ b/redfish-core/src/redfish.cpp
@@ -44,6 +44,7 @@
 #include "storage.hpp"
 #include "systems.hpp"
 #include "systems_logservices_hostlogger.hpp"
+#include "systems_logservices_journal_eventlog.hpp"
 #include "systems_logservices_postcodes.hpp"
 #include "task.hpp"
 #include "telemetry_service.hpp"