Add a BMC LogService

Adds a redfish LogService that provides the contents of the
BMC journald instance available over readfish.

This should be considered a prototype at this time, and suggestions on
how this could be done better are welcome.

At some point in the future, we could make this available over the
redfish TaskService via SSE.

Change-Id: Ibca0d473dcef545763256cc1df5574571a3af414
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Signed-off-by: Jason M. Bills <jason.m.bills@linux.intel.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 0998634..3aef06a 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -68,14 +68,17 @@
         nodes.emplace_back(
             std::make_unique<VlanNetworkInterfaceCollection>(app));
         nodes.emplace_back(std::make_unique<LogServiceCollection>(app));
+        nodes.emplace_back(std::make_unique<BMCLogService>(app));
+        nodes.emplace_back(std::make_unique<BMCLogEntryCollection>(app));
+        nodes.emplace_back(std::make_unique<BMCLogEntry>(app));
 
 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
-        nodes.emplace_back(std::make_unique<CpuLogService>(app));
-        nodes.emplace_back(std::make_unique<CpuLogEntryCollection>(app));
-        nodes.emplace_back(std::make_unique<CpuLogEntry>(app));
-        nodes.emplace_back(std::make_unique<ImmediateCpuLog>(app));
+        nodes.emplace_back(std::make_unique<CPULogService>(app));
+        nodes.emplace_back(std::make_unique<CPULogEntryCollection>(app));
+        nodes.emplace_back(std::make_unique<CPULogEntry>(app));
+        nodes.emplace_back(std::make_unique<ImmediateCPULog>(app));
 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
-        nodes.emplace_back(std::make_unique<SendRawPeci>(app));
+        nodes.emplace_back(std::make_unique<SendRawPECI>(app));
 #endif // BMCWEB_ENABLE_REDFISH_RAW_PECI
 #endif // BMCWEB_ENABLE_REDFISH_CPU_LOG
 
diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
index 37a3a54..ee342cf 100644
--- a/redfish-core/lib/log_services.hpp
+++ b/redfish-core/lib/log_services.hpp
@@ -17,7 +17,10 @@
 
 #include "node.hpp"
 
+#include <systemd/sd-journal.h>
+
 #include <boost/container/flat_map.hpp>
+#include <boost/utility/string_view.hpp>
 #include <experimental/filesystem>
 
 namespace redfish
@@ -29,7 +32,7 @@
 constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog";
 constexpr char const *cpuLogImmediateInterface =
     "com.intel.CpuDebugLog.Immediate";
-constexpr char const *cpuLogRawPeciInterface =
+constexpr char const *cpuLogRawPECIInterface =
     "com.intel.CpuDebugLog.SendRawPeci";
 
 namespace fs = std::experimental::filesystem;
@@ -45,12 +48,12 @@
         // load dynamic data so the duplicate static members don't get displayed
         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices";
         entityPrivileges = {
-            {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
     }
 
   private:
@@ -60,44 +63,346 @@
     void doGet(crow::Response &res, const crow::Request &req,
                const std::vector<std::string> &params) override
     {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
         // Collections don't include the static data added by SubRoute because
         // it has a duplicate entry for members
-        res.jsonValue["@odata.type"] =
+        asyncResp->res.jsonValue["@odata.type"] =
             "#LogServiceCollection.LogServiceCollection";
-        res.jsonValue["@odata.context"] =
+        asyncResp->res.jsonValue["@odata.context"] =
             "/redfish/v1/"
             "$metadata#LogServiceCollection.LogServiceCollection";
-        res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices";
-        res.jsonValue["Name"] = "Open BMC Log Services Collection";
-        res.jsonValue["Description"] =
+        asyncResp->res.jsonValue["@odata.id"] =
+            "/redfish/v1/Managers/bmc/LogServices";
+        asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
+        asyncResp->res.jsonValue["Description"] =
             "Collection of LogServices for this Manager";
-        nlohmann::json &logserviceArray = res.jsonValue["Members"];
+        nlohmann::json &logserviceArray = asyncResp->res.jsonValue["Members"];
         logserviceArray = nlohmann::json::array();
+        logserviceArray.push_back(
+            {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog"}});
 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
         logserviceArray.push_back(
             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
 #endif
-        res.jsonValue["Members@odata.count"] = logserviceArray.size();
-        res.end();
+        asyncResp->res.jsonValue["Members@odata.count"] =
+            logserviceArray.size();
     }
 };
 
-class CpuLogService : public Node
+class BMCLogService : public Node
 {
   public:
     template <typename CrowApp>
-    CpuLogService(CrowApp &app) :
-        Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog")
+    BMCLogService(CrowApp &app) :
+        Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/")
+    {
+        // Set the id for SubRoute
+        Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/BmcLog";
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
+        // Copy over the static data to include the entries added by SubRoute
+        asyncResp->res.jsonValue = Node::json;
+        asyncResp->res.jsonValue["@odata.type"] =
+            "#LogService.v1_1_0.LogService";
+        asyncResp->res.jsonValue["@odata.context"] =
+            "/redfish/v1/$metadata#LogService.LogService";
+        asyncResp->res.jsonValue["Name"] = "Open BMC Log Service";
+        asyncResp->res.jsonValue["Description"] = "BMC Log Service";
+        asyncResp->res.jsonValue["Id"] = "BMC Log";
+        asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
+    }
+};
+
+static int fillBMCLogEntryJson(const std::string &bmcLogEntryID,
+                               sd_journal *journal,
+                               nlohmann::json &bmcLogEntryJson)
+{
+    // Get the Log Entry contents
+    int ret = 0;
+    const char *data = nullptr;
+    size_t length = 0;
+
+    ret =
+        sd_journal_get_data(journal, "MESSAGE", (const void **)&data, &length);
+    if (ret < 0)
+    {
+        BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
+        return 1;
+    }
+    boost::string_view msg;
+    msg = boost::string_view(data, length);
+    // Only use the content after the "=" character.
+    msg.remove_prefix(std::min(msg.find("=") + 1, msg.size()));
+
+    // Get the severity from the PRIORITY field
+    boost::string_view priority;
+    int severity = 8; // Default to an invalid priority
+    ret =
+        sd_journal_get_data(journal, "PRIORITY", (const void **)&data, &length);
+    if (ret < 0)
+    {
+        BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
+        return 1;
+    }
+    priority = boost::string_view(data, length);
+    // Check length for sanity. Must be a single digit in the form
+    // "PRIORITY=[0-7]"
+    if (priority.size() > sizeof("PRIORITY=0"))
+    {
+        BMCWEB_LOG_ERROR << "Invalid PRIORITY field length";
+        return 1;
+    }
+    // Only use the content after the "=" character.
+    priority.remove_prefix(std::min(priority.find("=") + 1, priority.size()));
+    severity = strtol(priority.data(), nullptr, 10);
+
+    // Get the Created time from the timestamp
+    // Get the entry timestamp
+    uint64_t timestamp = 0;
+    ret = sd_journal_get_realtime_usec(journal, &timestamp);
+    if (ret < 0)
+    {
+        BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
+                         << strerror(-ret);
+    }
+    time_t t =
+        static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
+    struct tm *loctime = localtime(&t);
+    char entryTime[64] = {};
+    if (NULL != loctime)
+    {
+        strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
+    }
+    // Insert the ':' into the timezone
+    boost::string_view t1(entryTime);
+    boost::string_view t2(entryTime);
+    if (t1.size() > 2 && t2.size() > 2)
+    {
+        t1.remove_suffix(2);
+        t2.remove_prefix(t2.size() - 2);
+    }
+    const std::string entryTimeStr(t1.to_string() + ":" + t2.to_string());
+
+    // Fill in the log entry with the gathered data
+    bmcLogEntryJson = {
+        {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
+        {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
+        {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/" +
+                          bmcLogEntryID},
+        {"Name", "BMC Journal Entry"},
+        {"Id", bmcLogEntryID},
+        {"Message", msg.to_string()},
+        {"EntryType", "Oem"},
+        {"Severity",
+         severity <= 2 ? "Critical"
+                       : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
+        {"OemRecordFormat", "Intel BMC Journal Entry"},
+        {"Created", std::move(entryTimeStr)}};
+    return 0;
+}
+
+class BMCLogEntryCollection : public Node
+{
+  public:
+    template <typename CrowApp>
+    BMCLogEntryCollection(CrowApp &app) :
+        Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/")
+    {
+        // Collections use static ID for SubRoute to add to its parent, but only
+        // load dynamic data so the duplicate static members don't get displayed
+        Node::json["@odata.id"] =
+            "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
+        // 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.context"] =
+            "/redfish/v1/"
+            "$metadata#LogEntryCollection.LogEntryCollection";
+        asyncResp->res.jsonValue["@odata.id"] =
+            "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
+        asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
+        asyncResp->res.jsonValue["Description"] =
+            "Collection of BMC Journal Entries";
+        nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
+        logEntryArray = nlohmann::json::array();
+
+        // Go through the journal and use the timestamp to create a unique ID
+        // for each entry
+        sd_journal *journalTmp = nullptr;
+        int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
+        if (ret < 0)
+        {
+            BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
+            asyncResp->res.result(
+                boost::beast::http::status::internal_server_error);
+            return;
+        }
+        std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
+            journalTmp, sd_journal_close);
+        journalTmp = nullptr;
+        uint64_t prevTs = 0;
+        int index = 0;
+        SD_JOURNAL_FOREACH(journal.get())
+        {
+            // Get the entry timestamp
+            uint64_t curTs = 0;
+            ret = sd_journal_get_realtime_usec(journal.get(), &curTs);
+            if (ret < 0)
+            {
+                BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
+                                 << strerror(-ret);
+                continue;
+            }
+            // If the timestamp isn't unique, increment the index
+            if (curTs == prevTs)
+            {
+                index++;
+            }
+            else
+            {
+                // Otherwise, reset it
+                index = 0;
+            }
+            // Save the timestamp
+            prevTs = curTs;
+
+            std::string idStr(std::to_string(curTs));
+            if (index > 0)
+            {
+                idStr += "_" + std::to_string(index);
+            }
+            logEntryArray.push_back({});
+            nlohmann::json &bmcLogEntry = logEntryArray.back();
+            if (fillBMCLogEntryJson(idStr, journal.get(), bmcLogEntry) != 0)
+            {
+                asyncResp->res.result(
+                    boost::beast::http::status::internal_server_error);
+                return;
+            }
+        }
+        asyncResp->res.jsonValue["Members@odata.count"] = logEntryArray.size();
+    }
+};
+
+class BMCLogEntry : public Node
+{
+  public:
+    BMCLogEntry(CrowApp &app) :
+        Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/<str>/",
+             std::string())
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
+        if (params.size() != 1)
+        {
+            asyncResp->res.result(
+                boost::beast::http::status::internal_server_error);
+            return;
+        }
+        // Convert the unique ID back to a timestamp to find the entry
+        boost::string_view tsStr(params[0]);
+        boost::string_view indexStr(params[0]);
+        uint64_t ts = 0;
+        uint16_t index = 0;
+        auto underscorePos = tsStr.find("_");
+        if (underscorePos == tsStr.npos)
+        {
+            // Timestamp has no index
+            ts = strtoull(tsStr.data(), nullptr, 10);
+        }
+        else
+        {
+            // Timestamp has an index
+            tsStr.remove_suffix(tsStr.size() - underscorePos + 1);
+            ts = strtoull(tsStr.data(), nullptr, 10);
+            indexStr.remove_prefix(underscorePos + 1);
+            index =
+                static_cast<uint16_t>(strtoul(indexStr.data(), nullptr, 10));
+        }
+
+        sd_journal *journalTmp = nullptr;
+        int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
+        if (ret < 0)
+        {
+            BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
+            asyncResp->res.result(
+                boost::beast::http::status::internal_server_error);
+            return;
+        }
+        std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
+            journalTmp, sd_journal_close);
+        journalTmp = nullptr;
+        // Go to the timestamp in the log and move to the entry at the index
+        ret = sd_journal_seek_realtime_usec(journal.get(), ts);
+        for (int i = 0; i <= index; i++)
+        {
+            sd_journal_next(journal.get());
+        }
+        if (fillBMCLogEntryJson(params[0], journal.get(),
+                                asyncResp->res.jsonValue) != 0)
+        {
+            asyncResp->res.result(
+                boost::beast::http::status::internal_server_error);
+            return;
+        }
+    }
+};
+
+class CPULogService : public Node
+{
+  public:
+    template <typename CrowApp>
+    CPULogService(CrowApp &app) :
+        Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
     {
         // Set the id for SubRoute
         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog";
         entityPrivileges = {
-            {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
     }
 
   private:
@@ -107,17 +412,20 @@
     void doGet(crow::Response &res, const crow::Request &req,
                const std::vector<std::string> &params) override
     {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
         // Copy over the static data to include the entries added by SubRoute
-        res.jsonValue = Node::json;
-        res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
-        res.jsonValue["@odata.context"] = "/redfish/v1/"
-                                          "$metadata#LogService.LogService";
-        res.jsonValue["Name"] = "Open BMC CPU Log Service";
-        res.jsonValue["Description"] = "CPU Log Service";
-        res.jsonValue["Id"] = "CPU Log";
-        res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
-        res.jsonValue["MaxNumberOfRecords"] = 3;
-        res.jsonValue["Actions"] = {
+        asyncResp->res.jsonValue = Node::json;
+        asyncResp->res.jsonValue["@odata.type"] =
+            "#LogService.v1_1_0.LogService";
+        asyncResp->res.jsonValue["@odata.context"] =
+            "/redfish/v1/"
+            "$metadata#LogService.LogService";
+        asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
+        asyncResp->res.jsonValue["Description"] = "CPU Log Service";
+        asyncResp->res.jsonValue["Id"] = "CPU Log";
+        asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
+        asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
+        asyncResp->res.jsonValue["Actions"] = {
             {"Oem",
              {{"#CpuLog.Immediate",
                {{"target",
@@ -125,34 +433,33 @@
                  "CpuLog.Immediate"}}}}}};
 
 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
-        res.jsonValue["Actions"]["Oem"].push_back(
+        asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
             {"#CpuLog.SendRawPeci",
              {{"target",
                "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
                "CpuLog.SendRawPeci"}}});
 #endif
-        res.end();
     }
 };
 
-class CpuLogEntryCollection : public Node
+class CPULogEntryCollection : public Node
 {
   public:
     template <typename CrowApp>
-    CpuLogEntryCollection(CrowApp &app) :
-        Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries")
+    CPULogEntryCollection(CrowApp &app) :
+        Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
     {
         // Collections use static ID for SubRoute to add to its parent, but only
         // load dynamic data so the duplicate static members don't get displayed
         Node::json["@odata.id"] =
             "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
         entityPrivileges = {
-            {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
     }
 
   private:
@@ -162,52 +469,55 @@
     void doGet(crow::Response &res, const crow::Request &req,
                const std::vector<std::string> &params) override
     {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
         // Collections don't include the static data added by SubRoute because
         // it has a duplicate entry for members
-        auto getLogEntriesCallback =
-            [&res](const boost::system::error_code ec,
-                   const std::vector<std::string> &resp) {
-                if (ec)
+        auto getLogEntriesCallback = [asyncResp](
+                                         const boost::system::error_code ec,
+                                         const std::vector<std::string> &resp) {
+            if (ec)
+            {
+                if (ec.value() !=
+                    boost::system::errc::no_such_file_or_directory)
                 {
-                    if (ec.value() !=
-                        boost::system::errc::no_such_file_or_directory)
-                    {
-                        BMCWEB_LOG_DEBUG << "failed to get entries ec: "
-                                         << ec.message();
-                        res.result(
-                            boost::beast::http::status::internal_server_error);
-                        res.end();
-                        return;
-                    }
+                    BMCWEB_LOG_DEBUG << "failed to get entries ec: "
+                                     << ec.message();
+                    asyncResp->res.result(
+                        boost::beast::http::status::internal_server_error);
+                    return;
                 }
-                res.jsonValue["@odata.type"] =
-                    "#LogEntryCollection.LogEntryCollection";
-                res.jsonValue["@odata.context"] =
-                    "/redfish/v1/"
-                    "$metadata#LogEntryCollection.LogEntryCollection";
-                res.jsonValue["Name"] = "Open BMC CPU Log Entries";
-                res.jsonValue["Description"] = "Collection of CPU Log Entries";
-                nlohmann::json &logentryArray = res.jsonValue["Members"];
-                logentryArray = nlohmann::json::array();
-                for (const std::string &objpath : resp)
+            }
+            asyncResp->res.jsonValue["@odata.type"] =
+                "#LogEntryCollection.LogEntryCollection";
+            asyncResp->res.jsonValue["@odata.context"] =
+                "/redfish/v1/"
+                "$metadata#LogEntryCollection.LogEntryCollection";
+            asyncResp->res.jsonValue["@odata.id"] =
+                "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
+            asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
+            asyncResp->res.jsonValue["Description"] =
+                "Collection of CPU Log Entries";
+            nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
+            logEntryArray = nlohmann::json::array();
+            for (const std::string &objpath : resp)
+            {
+                // Don't list the immediate log
+                if (objpath.compare(cpuLogImmediatePath) == 0)
                 {
-                    // Don't list the immediate log
-                    if (objpath.compare(cpuLogImmediatePath) == 0)
-                    {
-                        continue;
-                    }
-                    std::size_t lastPos = objpath.rfind("/");
-                    if (lastPos != std::string::npos)
-                    {
-                        logentryArray.push_back(
-                            {{"@odata.id", "/redfish/v1/Managers/bmc/"
-                                           "LogServices/CpuLog/Entries/" +
-                                               objpath.substr(lastPos + 1)}});
-                    }
+                    continue;
                 }
-                res.jsonValue["Members@odata.count"] = logentryArray.size();
-                res.end();
-            };
+                std::size_t lastPos = objpath.rfind("/");
+                if (lastPos != std::string::npos)
+                {
+                    logEntryArray.push_back(
+                        {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
+                                       "CpuLog/Entries/" +
+                                           objpath.substr(lastPos + 1)}});
+                }
+            }
+            asyncResp->res.jsonValue["Members@odata.count"] =
+                logEntryArray.size();
+        };
         crow::connections::systemBus->async_method_call(
             std::move(getLogEntriesCallback),
             "xyz.openbmc_project.ObjectMapper",
@@ -237,73 +547,76 @@
     return std::string();
 }
 
-class CpuLogEntry : public Node
+class CPULogEntry : public Node
 {
   public:
-    CpuLogEntry(CrowApp &app) :
+    CPULogEntry(CrowApp &app) :
         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
              std::string())
     {
         entityPrivileges = {
-            {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
     }
 
   private:
     void doGet(crow::Response &res, const crow::Request &req,
                const std::vector<std::string> &params) override
     {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
         if (params.size() != 1)
         {
-            res.result(boost::beast::http::status::internal_server_error);
-            res.end();
+            asyncResp->res.result(
+                boost::beast::http::status::internal_server_error);
             return;
         }
         const uint8_t logId = std::atoi(params[0].c_str());
-        auto getStoredLogCallback = [&res,
-                                     logId](const boost::system::error_code ec,
-                                            const sdbusplus::message::variant<
-                                                std::string> &resp) {
-            if (ec)
-            {
-                BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
-                res.result(boost::beast::http::status::internal_server_error);
-                res.end();
-                return;
-            }
-            const std::string *log = mapbox::getPtr<const std::string>(resp);
-            if (log == nullptr)
-            {
-                res.result(boost::beast::http::status::internal_server_error);
-                res.end();
-                return;
-            }
-            nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
-            if (j.is_discarded())
-            {
-                res.result(boost::beast::http::status::internal_server_error);
-                res.end();
-                return;
-            }
-            std::string t = getLogCreatedTime(j);
-            res.jsonValue = {
-                {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
-                {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
-                {"@odata.id",
-                 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
-                     std::to_string(logId)},
-                {"Name", "CPU Debug Log"},
-                {"Id", logId},
-                {"EntryType", "Oem"},
-                {"OemRecordFormat", "Intel CPU Log"},
-                {"Oem", {{"Intel", std::move(j)}}},
-                {"Created", std::move(t)}};
-            res.end();
-        };
+        auto getStoredLogCallback =
+            [asyncResp,
+             logId](const boost::system::error_code ec,
+                    const sdbusplus::message::variant<std::string> &resp) {
+                if (ec)
+                {
+                    BMCWEB_LOG_DEBUG << "failed to get log ec: "
+                                     << ec.message();
+                    asyncResp->res.result(
+                        boost::beast::http::status::internal_server_error);
+                    return;
+                }
+                const std::string *log =
+                    mapbox::getPtr<const std::string>(resp);
+                if (log == nullptr)
+                {
+                    asyncResp->res.result(
+                        boost::beast::http::status::internal_server_error);
+                    return;
+                }
+                nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
+                if (j.is_discarded())
+                {
+                    asyncResp->res.result(
+                        boost::beast::http::status::internal_server_error);
+                    return;
+                }
+                std::string t = getLogCreatedTime(j);
+                asyncResp->res.jsonValue = {
+                    {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
+                    {"@odata.context",
+                     "/redfish/v1/$metadata#LogEntry.LogEntry"},
+                    {"@odata.id",
+                     "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
+                         std::to_string(logId)},
+                    {"Name", "CPU Debug Log"},
+                    {"Id", logId},
+                    {"EntryType", "Oem"},
+                    {"OemRecordFormat", "Intel CPU Log"},
+                    {"Oem", {{"Intel", std::move(j)}}},
+                    {"Created", std::move(t)}};
+            };
         crow::connections::systemBus->async_method_call(
             std::move(getStoredLogCallback), cpuLogObject,
             cpuLogPath + std::string("/") + std::to_string(logId),
@@ -311,45 +624,47 @@
     }
 };
 
-class ImmediateCpuLog : public Node
+class ImmediateCPULog : public Node
 {
   public:
-    ImmediateCpuLog(CrowApp &app) :
+    ImmediateCPULog(CrowApp &app) :
         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
-                  "CpuLog.Immediate")
+                  "CpuLog.Immediate/")
     {
         entityPrivileges = {
-            {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
-            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
     }
 
   private:
     void doPost(crow::Response &res, const crow::Request &req,
                 const std::vector<std::string> &params) override
     {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
         static std::unique_ptr<sdbusplus::bus::match::match>
             immediateLogMatcher;
 
         // Only allow one Immediate Log request at a time
         if (immediateLogMatcher != nullptr)
         {
-            res.addHeader("Retry-After", "30");
-            res.result(boost::beast::http::status::service_unavailable);
+            asyncResp->res.addHeader("Retry-After", "30");
+            asyncResp->res.result(
+                boost::beast::http::status::service_unavailable);
             messages::addMessageToJson(
-                res.jsonValue, messages::serviceTemporarilyUnavailable("30"),
+                asyncResp->res.jsonValue,
+                messages::serviceTemporarilyUnavailable("30"),
                 "/CpuLog.Immediate");
-            res.end();
             return;
         }
         // Make this static so it survives outside this method
         static boost::asio::deadline_timer timeout(*req.ioService);
 
         timeout.expires_from_now(boost::posix_time::seconds(30));
-        timeout.async_wait([&res](const boost::system::error_code &ec) {
+        timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
             immediateLogMatcher = nullptr;
             if (ec)
             {
@@ -363,11 +678,11 @@
             }
             BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
 
-            res.result(boost::beast::http::status::internal_server_error);
-            res.end();
+            asyncResp->res.result(
+                boost::beast::http::status::internal_server_error);
         });
 
-        auto immediateLogMatcherCallback = [&res](
+        auto immediateLogMatcherCallback = [asyncResp](
                                                sdbusplus::message::message &m) {
             BMCWEB_LOG_DEBUG << "Immediate log available match fired";
             boost::system::error_code ec;
@@ -387,8 +702,8 @@
                 interfacesAdded[cpuLogInterface]["Log"]);
             if (log == nullptr)
             {
-                res.result(boost::beast::http::status::internal_server_error);
-                res.end();
+                asyncResp->res.result(
+                    boost::beast::http::status::internal_server_error);
                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
                 // match object inside which this lambda is executing.  Once it
                 // is set to nullptr, the match object will be destroyed and the
@@ -400,8 +715,8 @@
             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
             if (j.is_discarded())
             {
-                res.result(boost::beast::http::status::internal_server_error);
-                res.end();
+                asyncResp->res.result(
+                    boost::beast::http::status::internal_server_error);
                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
                 // match object inside which this lambda is executing.  Once it
                 // is set to nullptr, the match object will be destroyed and the
@@ -411,7 +726,7 @@
                 return;
             }
             std::string t = getLogCreatedTime(j);
-            res.jsonValue = {
+            asyncResp->res.jsonValue = {
                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
                 {"Name", "CPU Debug Log"},
@@ -419,7 +734,6 @@
                 {"OemRecordFormat", "Intel CPU Log"},
                 {"Oem", {{"Intel", std::move(j)}}},
                 {"Created", std::move(t)}};
-            res.end();
             // Careful with immediateLogMatcher.  It is a unique_ptr to the
             // match object inside which this lambda is executing.  Once it is
             // set to nullptr, the match object will be destroyed and the lambda
@@ -434,25 +748,24 @@
             std::move(immediateLogMatcherCallback));
 
         auto generateImmediateLogCallback =
-            [&res](const boost::system::error_code ec,
-                   const std::string &resp) {
+            [asyncResp](const boost::system::error_code ec,
+                        const std::string &resp) {
                 if (ec)
                 {
                     if (ec.value() ==
                         boost::system::errc::operation_not_supported)
                     {
                         messages::addMessageToJson(
-                            res.jsonValue, messages::resourceInStandby(),
-                            "/CpuLog.Immediate");
-                        res.result(
+                            asyncResp->res.jsonValue,
+                            messages::resourceInStandby(), "/CpuLog.Immediate");
+                        asyncResp->res.result(
                             boost::beast::http::status::service_unavailable);
                     }
                     else
                     {
-                        res.result(
+                        asyncResp->res.result(
                             boost::beast::http::status::internal_server_error);
                     }
-                    res.end();
                     boost::system::error_code timeoutec;
                     timeout.cancel(timeoutec);
                     if (timeoutec)
@@ -470,12 +783,12 @@
     }
 };
 
-class SendRawPeci : public Node
+class SendRawPECI : public Node
 {
   public:
-    SendRawPeci(CrowApp &app) :
+    SendRawPECI(CrowApp &app) :
         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
-                  "CpuLog.SendRawPeci")
+                  "CpuLog.SendRawPeci/")
     {
         entityPrivileges = {
             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
@@ -490,67 +803,63 @@
     void doPost(crow::Response &res, const crow::Request &req,
                 const std::vector<std::string> &params) override
     {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
         // Get the Raw PECI command from the request
-        nlohmann::json rawPeciCmd;
-        if (!json_util::processJsonFromRequest(res, req, rawPeciCmd))
+        nlohmann::json rawPECICmd;
+        if (!json_util::processJsonFromRequest(res, req, rawPECICmd))
         {
             return;
         }
         // Get the Client Address from the request
-        nlohmann::json::const_iterator caIt = rawPeciCmd.find("ClientAddress");
-        if (caIt == rawPeciCmd.end())
+        nlohmann::json::const_iterator caIt = rawPECICmd.find("ClientAddress");
+        if (caIt == rawPECICmd.end())
         {
             messages::addMessageToJson(
-                res.jsonValue, messages::propertyMissing("ClientAddress"),
-                "/ClientAddress");
-            res.result(boost::beast::http::status::bad_request);
-            res.end();
+                asyncResp->res.jsonValue,
+                messages::propertyMissing("ClientAddress"), "/ClientAddress");
+            asyncResp->res.result(boost::beast::http::status::bad_request);
             return;
         }
         const uint64_t *ca = caIt->get_ptr<const uint64_t *>();
         if (ca == nullptr)
         {
             messages::addMessageToJson(
-                res.jsonValue,
+                asyncResp->res.jsonValue,
                 messages::propertyValueTypeError(caIt->dump(), "ClientAddress"),
                 "/ClientAddress");
-            res.result(boost::beast::http::status::bad_request);
-            res.end();
+            asyncResp->res.result(boost::beast::http::status::bad_request);
             return;
         }
         // Get the Read Length from the request
         const uint8_t clientAddress = static_cast<uint8_t>(*ca);
-        nlohmann::json::const_iterator rlIt = rawPeciCmd.find("ReadLength");
-        if (rlIt == rawPeciCmd.end())
+        nlohmann::json::const_iterator rlIt = rawPECICmd.find("ReadLength");
+        if (rlIt == rawPECICmd.end())
         {
-            messages::addMessageToJson(res.jsonValue,
+            messages::addMessageToJson(asyncResp->res.jsonValue,
                                        messages::propertyMissing("ReadLength"),
                                        "/ReadLength");
-            res.result(boost::beast::http::status::bad_request);
-            res.end();
+            asyncResp->res.result(boost::beast::http::status::bad_request);
             return;
         }
         const uint64_t *rl = rlIt->get_ptr<const uint64_t *>();
         if (rl == nullptr)
         {
             messages::addMessageToJson(
-                res.jsonValue,
+                asyncResp->res.jsonValue,
                 messages::propertyValueTypeError(rlIt->dump(), "ReadLength"),
                 "/ReadLength");
-            res.result(boost::beast::http::status::bad_request);
-            res.end();
+            asyncResp->res.result(boost::beast::http::status::bad_request);
             return;
         }
         // Get the PECI Command from the request
         const uint32_t readLength = static_cast<uint32_t>(*rl);
-        nlohmann::json::const_iterator pcIt = rawPeciCmd.find("PECICommand");
-        if (pcIt == rawPeciCmd.end())
+        nlohmann::json::const_iterator pcIt = rawPECICmd.find("PECICommand");
+        if (pcIt == rawPECICmd.end())
         {
-            messages::addMessageToJson(res.jsonValue,
+            messages::addMessageToJson(asyncResp->res.jsonValue,
                                        messages::propertyMissing("PECICommand"),
                                        "/PECICommand");
-            res.result(boost::beast::http::status::bad_request);
-            res.end();
+            asyncResp->res.result(boost::beast::http::status::bad_request);
             return;
         }
         std::vector<uint8_t> peciCommand;
@@ -560,36 +869,35 @@
             if (val == nullptr)
             {
                 messages::addMessageToJson(
-                    res.jsonValue,
+                    asyncResp->res.jsonValue,
                     messages::propertyValueTypeError(
                         pc.dump(),
                         "PECICommand/" + std::to_string(peciCommand.size())),
                     "/PECICommand");
-                res.result(boost::beast::http::status::bad_request);
-                res.end();
+                asyncResp->res.result(boost::beast::http::status::bad_request);
                 return;
             }
             peciCommand.push_back(static_cast<uint8_t>(*val));
         }
         // Callback to return the Raw PECI response
-        auto sendRawPeciCallback = [&res](const boost::system::error_code ec,
-                                          const std::vector<uint8_t> &resp) {
-            if (ec)
-            {
-                BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
-                                 << ec.message();
-                res.result(boost::beast::http::status::internal_server_error);
-                res.end();
-                return;
-            }
-            res.jsonValue = {{"Name", "PECI Command Response"},
-                             {"PECIResponse", resp}};
-            res.end();
-        };
+        auto sendRawPECICallback =
+            [asyncResp](const boost::system::error_code ec,
+                        const std::vector<uint8_t> &resp) {
+                if (ec)
+                {
+                    BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
+                                     << ec.message();
+                    asyncResp->res.result(
+                        boost::beast::http::status::internal_server_error);
+                    return;
+                }
+                asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
+                                            {"PECIResponse", resp}};
+            };
         // Call the SendRawPECI command with the provided data
         crow::connections::systemBus->async_method_call(
-            std::move(sendRawPeciCallback), cpuLogObject, cpuLogPath,
-            cpuLogRawPeciInterface, "SendRawPeci", clientAddress, readLength,
+            std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
+            cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
             peciCommand);
     }
 };