Initial redfish logging support

This was imported from a fork:
https://github.com/ampere-openbmc/bmcweb/commits/ampere-next/redfish-core/lib/logservices.hpp

Which had a series of commits from TungVuX and hyche

The initial patch is that code verbatim.
Follow up patches on top of this make the necessary changes
for current bmcweb:
- Move to sdbusplus
- C++ naming convention changes
- Clang format
- Some refactoring

Tested:
  Verified new services work correctly when queried
    e.g. curl -k -H "X-Auth-Token: $bmc_token" -X GET \
    https://${bmc}/redfish/v1/Systems/system/LogServices/EventLog/Entries
    displays the entries properly.

    RedfishServiceValidator results:
      /redfish/v1/Systems/system/LogServices
        pass: 3
        passGet: 1
        skipOptional: 1
      /redfish/v1/Systems/system/LogServices/EventLog
        pass: 5
        passGet: 1
        skipOptional: 8
      /redfish/v1/Systems/system/LogServices/EventLog/Entries
        pass: 3
        passGet: 1
        skipOptional: 1
      /redfish/v1/Systems/system/LogServices/EventLog/<str>
        pass: 6
        passGet: 1
        skipOptional: 16

Sample Output: curl -k -H "X-Auth-Token: $bmc_token" -X GET https://${bmc}/redfish/v1/Systems/system/LogServices/EventLog/Entries
{
  "@odata.context": "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection",
  "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries",
  "@odata.type": "#LogEntryCollection.LogEntryCollection",
  "Description": "Collection of System Event Log Entries",
  "Members": [
    {
      "@odata.context": "/redfish/v1/$metadata#LogEntry.LogEntry",
      "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/1",
      "@odata.type": "#LogEntry.v1_4_0.LogEntry",
      "Created": "2019-02-22T17:11:00+00:00",
      "EntryType": "Event",
      "Id": "1",
      "Message": "example.xyz.openbmc_project.Example.Elog.AutoTestSimple",
      "Name": "System DBus Event Log Entry",
      "Severity": "Critical"
    }
  ],
  "Members@odata.count": 1,
  "Name": "System Event Log Entries"
}

Change-Id: I422b0d0ec577ea734fecfb6f49101ec5ff45a556
Signed-off-by: Andrew Geissler <geissonator@yahoo.com>
Signed-off-by: Anthony Wilson <wilsonan@us.ibm.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 70e56fd..28fbe0f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,6 +35,9 @@
 option (BMCWEB_ENABLE_REDFISH_ONE_CHASSIS "Enable Redfish to assume only one
        chassis is present.  All sensors will be assumed to be under this
        chassis.  The chassis path is '/redfish/v1/Chassis/chassis'." OFF)
+option (BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES "Enable DBUS log service
+        transactions through Redfish. Paths are under
+        '/redfish/v1/Systems/system/LogServices/EventLog/Entries'." OFF)
 
 # Insecure options.  Every option that starts with a BMCWEB_INSECURE flag should
 # not be enabled by default for any platform, unless the author fully
@@ -267,4 +270,6 @@
     $<$<BOOL:${BMCWEB_ENABLE_REDFISH_CPU_LOG}>: -DBMCWEB_ENABLE_REDFISH_CPU_LOG>
     $<$<BOOL:${BMCWEB_ENABLE_REDFISH_BMC_JOURNAL}>: -DBMCWEB_ENABLE_REDFISH_BMC_JOURNAL>
     $<$<BOOL:${BMCWEB_ENABLE_REDFISH_ONE_CHASSIS}>: -DBMCWEB_ENABLE_REDFISH_ONE_CHASSIS>
+    $<$<BOOL:${BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES}>: -DBMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES>
+
 )
diff --git a/crow/include/crow/utility.h b/crow/include/crow/utility.h
index a07c041..63d2dec 100644
--- a/crow/include/crow/utility.h
+++ b/crow/include/crow/utility.h
@@ -2,7 +2,6 @@
 
 #include "nlohmann/json.hpp"
 
-#include <boost/utility/string_view.hpp>
 #include <cstdint>
 #include <cstring>
 #include <functional>
@@ -712,5 +711,34 @@
     s = std::regex_replace(s, nextLink, "$1<a href=\"$5\">$4</a>");
 }
 
+/**
+ * Method returns Date Time information according to requested format
+ *
+ * @param[in] time time in second since the Epoch
+ *
+ * @return Date Time according to requested format
+ */
+inline std::string getDateTime(const std::time_t& time)
+{
+    std::array<char, 128> dateTime;
+    std::string redfishDateTime("0000-00-00T00:00:00Z00:00");
+
+    if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z",
+                      std::localtime(&time)))
+    {
+        // insert the colon required by the ISO 8601 standard
+        redfishDateTime = std::string(dateTime.data());
+        redfishDateTime.insert(redfishDateTime.end() - 2, ':');
+    }
+
+    return redfishDateTime;
+}
+
+inline std::string dateTimeNow()
+{
+    std::time_t time = std::time(nullptr);
+    return getDateTime(time);
+}
+
 } // namespace utility
 } // namespace crow
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 36b50e8..38832c2 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -103,6 +103,9 @@
         nodes.emplace_back(std::make_unique<SystemsCollection>(app));
         nodes.emplace_back(std::make_unique<Systems>(app));
         nodes.emplace_back(std::make_unique<SystemActionsReset>(app));
+#ifdef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
+        nodes.emplace_back(std::make_unique<DBusLogServiceActionsClear>(app));
+#endif
 
         for (const auto& node : nodes)
         {
diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
index 61cbd9e..d515028 100644
--- a/redfish-core/lib/log_services.hpp
+++ b/redfish-core/lib/log_services.hpp
@@ -21,7 +21,7 @@
 #include <systemd/sd-journal.h>
 
 #include <boost/container/flat_map.hpp>
-#include <boost/utility/string_view.hpp>
+#include <error_messages.hpp>
 #include <variant>
 
 namespace redfish
@@ -38,6 +38,52 @@
 
 namespace fs = std::filesystem;
 
+using GetManagedPropertyType = boost::container::flat_map<
+    std::string,
+    sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
+                                int32_t, uint32_t, int64_t, uint64_t, double>>;
+
+using GetManagedObjectsType = boost::container::flat_map<
+    sdbusplus::message::object_path,
+    boost::container::flat_map<std::string, GetManagedPropertyType>>;
+
+inline std::string translateSeverityDbusToRedfish(const std::string &s)
+{
+    if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
+    {
+        return "Critical";
+    }
+    else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
+    {
+        return "Critical";
+    }
+    else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
+    {
+        return "OK";
+    }
+    else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
+    {
+        return "Critical";
+    }
+    else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
+    {
+        return "Critical";
+    }
+    else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
+    {
+        return "OK";
+    }
+    else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
+    {
+        return "OK";
+    }
+    else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
+    {
+        return "Warning";
+    }
+    return "";
+}
+
 static int getJournalMetadata(sd_journal *journal,
                               const std::string_view &field,
                               std::string_view &contents)
@@ -296,7 +342,8 @@
             {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
         logServiceArray.push_back(
-            {{"@odata.id", "/redfish/v1/Systems/system/LogServices/CpuLog"}});
+            {{ "@odata.id",
+               "/redfish/v1/Systems/system/LogServices/CpuLog" }});
 #endif
         asyncResp->res.jsonValue["Members@odata.count"] =
             logServiceArray.size();
@@ -407,7 +454,7 @@
 
     // Fill in the log entry with the gathered data
     bmcLogEntryJson = {
-        {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
+        {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
         {"@odata.id",
          "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
@@ -467,9 +514,10 @@
         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
         asyncResp->res.jsonValue["Description"] =
             "Collection of System Event Log Entries";
+
+#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
         logEntryArray = nlohmann::json::array();
-
         // Go through the journal and create a unique ID for each entry
         sd_journal *journalTmp = nullptr;
         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
@@ -525,6 +573,120 @@
                 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" +
                 std::to_string(skip + top);
         }
+#else
+        // DBus implementation of EventLog/Entries
+        // Make call to Logging Service to find all log entry objects
+        crow::connections::systemBus->async_method_call(
+            [asyncResp](const boost::system::error_code ec,
+                        GetManagedObjectsType &resp) {
+                if (ec)
+                {
+                    // TODO Handle for specific error code
+                    BMCWEB_LOG_ERROR
+                        << "getLogEntriesIfaceData resp_handler got error "
+                        << ec;
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+                nlohmann::json &entriesArray =
+                    asyncResp->res.jsonValue["Members"];
+                entriesArray = nlohmann::json::array();
+                for (auto &objectPath : resp)
+                {
+                    for (auto &interfaceMap : objectPath.second)
+                    {
+                        if (interfaceMap.first !=
+                            "xyz.openbmc_project.Logging.Entry")
+                        {
+                            BMCWEB_LOG_DEBUG << "Bailing early on "
+                                             << interfaceMap.first;
+                            continue;
+                        }
+                        entriesArray.push_back({});
+                        nlohmann::json &thisEntry = entriesArray.back();
+                        uint32_t *id;
+                        std::time_t timestamp;
+                        std::string *severity, *message;
+                        bool *resolved;
+                        for (auto &propertyMap : interfaceMap.second)
+                        {
+                            if (propertyMap.first == "Id")
+                            {
+                                id = sdbusplus::message::variant_ns::get_if<
+                                    uint32_t>(&propertyMap.second);
+                                if (id == nullptr)
+                                {
+                                    messages::propertyMissing(asyncResp->res,
+                                                              "Id");
+                                }
+                            }
+                            else if (propertyMap.first == "Timestamp")
+                            {
+                                const uint64_t *millisTimeStamp =
+                                    std::get_if<uint64_t>(&propertyMap.second);
+                                if (millisTimeStamp == nullptr)
+                                {
+                                    messages::propertyMissing(asyncResp->res,
+                                                              "Timestamp");
+                                }
+                                // Retrieve Created property with format:
+                                // yyyy-mm-ddThh:mm:ss
+                                std::chrono::milliseconds chronoTimeStamp(
+                                    *millisTimeStamp);
+                                timestamp =
+                                    std::chrono::duration_cast<
+                                        std::chrono::seconds>(chronoTimeStamp)
+                                        .count();
+                            }
+                            else if (propertyMap.first == "Severity")
+                            {
+                                severity = std::get_if<std::string>(
+                                    &propertyMap.second);
+                                if (severity == nullptr)
+                                {
+                                    messages::propertyMissing(asyncResp->res,
+                                                              "Severity");
+                                }
+                            }
+                            else if (propertyMap.first == "Message")
+                            {
+                                message = std::get_if<std::string>(
+                                    &propertyMap.second);
+                                if (message == nullptr)
+                                {
+                                    messages::propertyMissing(asyncResp->res,
+                                                              "Message");
+                                }
+                            }
+                        }
+                        thisEntry = {
+                            {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
+                            {"@odata.context", "/redfish/v1/"
+                                               "$metadata#LogEntry.LogEntry"},
+                            {"@odata.id",
+                             "/redfish/v1/Systems/system/LogServices/EventLog/"
+                             "Entries/" +
+                                 std::to_string(*id)},
+                            {"Name", "System DBus Event Log Entry"},
+                            {"Id", std::to_string(*id)},
+                            {"Message", *message},
+                            {"EntryType", "Event"},
+                            {"Severity",
+                             translateSeverityDbusToRedfish(*severity)},
+                            {"Created", crow::utility::getDateTime(timestamp)}};
+                    }
+                }
+                std::sort(entriesArray.begin(), entriesArray.end(),
+                          [](const nlohmann::json &left,
+                             const nlohmann::json &right) {
+                              return (left["Id"] <= right["Id"]);
+                          });
+                asyncResp->res.jsonValue["Members@odata.count"] =
+                    entriesArray.size();
+            },
+            "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
+            "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+#endif
     }
 };
 
@@ -556,6 +718,8 @@
             return;
         }
         const std::string &entryID = params[0];
+
+#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
         // Convert the unique ID back to a timestamp to find the entry
         uint64_t ts = 0;
         uint16_t index = 0;
@@ -605,6 +769,91 @@
             messages::internalError(asyncResp->res);
             return;
         }
+#else
+        // DBus implementation of EventLog/Entries
+        // Make call to Logging Service to find all log entry objects
+        crow::connections::systemBus->async_method_call(
+            [asyncResp, entryID](const boost::system::error_code ec,
+                                 GetManagedPropertyType &resp) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR
+                        << "EventLogEntry (DBus) resp_handler got error " << ec;
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+                uint32_t *id;
+                std::time_t timestamp;
+                std::string *severity, *message;
+                bool *resolved;
+                for (auto &propertyMap : resp)
+                {
+                    if (propertyMap.first == "Id")
+                    {
+                        id = std::get_if<uint32_t>(&propertyMap.second);
+                        if (id == nullptr)
+                        {
+                            messages::propertyMissing(asyncResp->res, "Id");
+                        }
+                    }
+                    else if (propertyMap.first == "Timestamp")
+                    {
+                        const uint64_t *millisTimeStamp =
+                            std::get_if<uint64_t>(&propertyMap.second);
+                        if (millisTimeStamp == nullptr)
+                        {
+                            messages::propertyMissing(asyncResp->res,
+                                                      "Timestamp");
+                        }
+                        // Retrieve Created property with format:
+                        // yyyy-mm-ddThh:mm:ss
+                        std::chrono::milliseconds chronoTimeStamp(
+                            *millisTimeStamp);
+                        timestamp =
+                            std::chrono::duration_cast<std::chrono::seconds>(
+                                chronoTimeStamp)
+                                .count();
+                    }
+                    else if (propertyMap.first == "Severity")
+                    {
+                        severity =
+                            std::get_if<std::string>(&propertyMap.second);
+                        if (severity == nullptr)
+                        {
+                            messages::propertyMissing(asyncResp->res,
+                                                      "Severity");
+                        }
+                    }
+                    else if (propertyMap.first == "Message")
+                    {
+                        message = std::get_if<std::string>(&propertyMap.second);
+                        if (message == nullptr)
+                        {
+                            messages::propertyMissing(asyncResp->res,
+                                                      "Message");
+                        }
+                    }
+                }
+                asyncResp->res.jsonValue = {
+                    {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
+                    {"@odata.context", "/redfish/v1/"
+                                       "$metadata#LogEntry.LogEntry"},
+                    {"@odata.id",
+                     "/redfish/v1/Systems/system/LogServices/EventLog/"
+                     "Entries/" +
+                         std::to_string(*id)},
+                    {"Name", "System DBus Event Log Entry"},
+                    {"Id", std::to_string(*id)},
+                    {"Message", *message},
+                    {"EntryType", "Event"},
+                    {"Severity", translateSeverityDbusToRedfish(*severity)},
+                    {"Created", crow::utility::getDateTime(timestamp)}};
+            },
+            "xyz.openbmc_project.Logging",
+            "/xyz/openbmc_project/logging/entry/" + entryID,
+            "org.freedesktop.DBus.Properties", "GetAll",
+            "xyz.openbmc_project.Logging.Entry");
+#endif
     }
 };
 
@@ -647,7 +896,8 @@
         logServiceArray = nlohmann::json::array();
 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
         logServiceArray.push_back(
-            {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
+            {{ "@odata.id",
+               "/redfish/v1/Managers/bmc/LogServices/Journal" }});
 #endif
         asyncResp->res.jsonValue["Members@odata.count"] =
             logServiceArray.size();
@@ -724,7 +974,7 @@
 
     // Fill in the log entry with the gathered data
     bmcJournalLogEntryJson = {
-        {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
+        {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
                           bmcJournalLogEntryID},
@@ -956,8 +1206,9 @@
 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
             {"#CpuLog.SendRawPeci",
-             {{"target", "/redfish/v1/Systems/system/LogServices/CpuLog/"
-                         "Actions/Oem/CpuLog.SendRawPeci"}}});
+             { { "target",
+                 "/redfish/v1/Systems/system/LogServices/CpuLog/"
+                 "Actions/Oem/CpuLog.SendRawPeci" } }});
 #endif
     }
 };
@@ -1118,7 +1369,7 @@
             }
             std::string t = getLogCreatedTime(j);
             asyncResp->res.jsonValue = {
-                {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
+                {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
                 {"@odata.id",
                  "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/" +
@@ -1231,7 +1482,7 @@
             }
             std::string t = getLogCreatedTime(j);
             asyncResp->res.jsonValue = {
-                {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
+                {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
                 {"Name", "CPU Debug Log"},
                 {"EntryType", "Oem"},
@@ -1335,4 +1586,57 @@
     }
 };
 
+/**
+ * DBusLogServiceActionsClear class supports POST method for ClearLog action.
+ */
+class DBusLogServiceActionsClear : public Node
+{
+  public:
+    DBusLogServiceActionsClear(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
+                  "LogService.Reset")
+    {
+        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:
+    /**
+     * Function handles POST method request.
+     * The Clear Log actions does not require any parameter.The action deletes
+     * all entries found in the Entries collection for this Log Service.
+     */
+    void doPost(crow::Response &res, const crow::Request &req,
+                const std::vector<std::string> &params) override
+    {
+        BMCWEB_LOG_DEBUG << "Do delete all entries.";
+
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+        // Process response from Logging service.
+        auto resp_handler = [asyncResp](const boost::system::error_code ec) {
+            BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
+            if (ec)
+            {
+                // TODO Handle for specific error code
+                BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
+                asyncResp->res.result(
+                    boost::beast::http::status::internal_server_error);
+                return;
+            }
+
+            asyncResp->res.result(boost::beast::http::status::no_content);
+        };
+
+        // Make call to Logging service to request Clear Log
+        crow::connections::systemBus->async_method_call(
+            resp_handler, "xyz.openbmc_project.Logging",
+            "/xyz/openbmc_project/logging",
+            "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
+    }
+};
 } // namespace redfish
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index 3cde0ef..e6e80a8 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -971,7 +971,7 @@
         manager_reset["ResetType@Redfish.AllowableValues"] = {
             "GracefulRestart"};
 
-        res.jsonValue["DateTime"] = getDateTime();
+        res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
         res.jsonValue["Links"]["ManagerForServers@odata.count"] = 1;
         res.jsonValue["Links"]["ManagerForServers"] = {
             {{"@odata.id", "/redfish/v1/Systems/system"}}};
@@ -1268,23 +1268,6 @@
         }
     }
 
-    std::string getDateTime() const
-    {
-        std::array<char, 128> dateTime;
-        std::string redfishDateTime("0000-00-00T00:00:00Z00:00");
-        std::time_t time = std::time(nullptr);
-
-        if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z",
-                          std::localtime(&time)))
-        {
-            // insert the colon required by the ISO 8601 standard
-            redfishDateTime = std::string(dateTime.data());
-            redfishDateTime.insert(redfishDateTime.end() - 2, ':');
-        }
-
-        return redfishDateTime;
-    }
-
     std::string uuid;
 };