xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision b6a61a5ec6aa3509c7dccad521e3850c71008a7a)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 #include "registries.hpp"
20 #include "registries/base_message_registry.hpp"
21 #include "registries/openbmc_message_registry.hpp"
22 
23 #include <systemd/sd-journal.h>
24 
25 #include <boost/algorithm/string/split.hpp>
26 #include <boost/beast/core/span.hpp>
27 #include <boost/container/flat_map.hpp>
28 #include <error_messages.hpp>
29 #include <filesystem>
30 #include <string_view>
31 #include <variant>
32 
33 namespace redfish
34 {
35 
36 constexpr char const *CrashdumpObject = "com.intel.crashdump";
37 constexpr char const *CrashdumpPath = "/com/intel/crashdump";
38 constexpr char const *CrashdumpOnDemandPath = "/com/intel/crashdump/OnDemand";
39 constexpr char const *CrashdumpInterface = "com.intel.crashdump";
40 constexpr char const *CrashdumpOnDemandInterface =
41     "com.intel.crashdump.OnDemand";
42 constexpr char const *CrashdumpRawPECIInterface =
43     "com.intel.crashdump.SendRawPeci";
44 
45 namespace message_registries
46 {
47 static const Message *getMessageFromRegistry(
48     const std::string &messageKey,
49     const boost::beast::span<const MessageEntry> registry)
50 {
51     boost::beast::span<const MessageEntry>::const_iterator messageIt =
52         std::find_if(registry.cbegin(), registry.cend(),
53                      [&messageKey](const MessageEntry &messageEntry) {
54                          return !std::strcmp(messageEntry.first,
55                                              messageKey.c_str());
56                      });
57     if (messageIt != registry.cend())
58     {
59         return &messageIt->second;
60     }
61 
62     return nullptr;
63 }
64 
65 static const Message *getMessage(const std::string_view &messageID)
66 {
67     // Redfish MessageIds are in the form
68     // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
69     // the right Message
70     std::vector<std::string> fields;
71     fields.reserve(4);
72     boost::split(fields, messageID, boost::is_any_of("."));
73     std::string &registryName = fields[0];
74     std::string &messageKey = fields[3];
75 
76     // Find the right registry and check it for the MessageKey
77     if (std::string(base::header.registryPrefix) == registryName)
78     {
79         return getMessageFromRegistry(
80             messageKey, boost::beast::span<const MessageEntry>(base::registry));
81     }
82     if (std::string(openbmc::header.registryPrefix) == registryName)
83     {
84         return getMessageFromRegistry(
85             messageKey,
86             boost::beast::span<const MessageEntry>(openbmc::registry));
87     }
88     return nullptr;
89 }
90 } // namespace message_registries
91 
92 namespace fs = std::filesystem;
93 
94 using GetManagedPropertyType = boost::container::flat_map<
95     std::string,
96     sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
97                                 int32_t, uint32_t, int64_t, uint64_t, double>>;
98 
99 using GetManagedObjectsType = boost::container::flat_map<
100     sdbusplus::message::object_path,
101     boost::container::flat_map<std::string, GetManagedPropertyType>>;
102 
103 inline std::string translateSeverityDbusToRedfish(const std::string &s)
104 {
105     if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
106     {
107         return "Critical";
108     }
109     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
110     {
111         return "Critical";
112     }
113     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
114     {
115         return "OK";
116     }
117     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
118     {
119         return "Critical";
120     }
121     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
122     {
123         return "Critical";
124     }
125     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
126     {
127         return "OK";
128     }
129     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
130     {
131         return "OK";
132     }
133     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
134     {
135         return "Warning";
136     }
137     return "";
138 }
139 
140 static int getJournalMetadata(sd_journal *journal,
141                               const std::string_view &field,
142                               std::string_view &contents)
143 {
144     const char *data = nullptr;
145     size_t length = 0;
146     int ret = 0;
147     // Get the metadata from the requested field of the journal entry
148     ret = sd_journal_get_data(journal, field.data(), (const void **)&data,
149                               &length);
150     if (ret < 0)
151     {
152         return ret;
153     }
154     contents = std::string_view(data, length);
155     // Only use the content after the "=" character.
156     contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
157     return ret;
158 }
159 
160 static int getJournalMetadata(sd_journal *journal,
161                               const std::string_view &field, const int &base,
162                               int &contents)
163 {
164     int ret = 0;
165     std::string_view metadata;
166     // Get the metadata from the requested field of the journal entry
167     ret = getJournalMetadata(journal, field, metadata);
168     if (ret < 0)
169     {
170         return ret;
171     }
172     contents = strtol(metadata.data(), nullptr, base);
173     return ret;
174 }
175 
176 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
177 {
178     int ret = 0;
179     uint64_t timestamp = 0;
180     ret = sd_journal_get_realtime_usec(journal, &timestamp);
181     if (ret < 0)
182     {
183         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
184                          << strerror(-ret);
185         return false;
186     }
187     time_t t =
188         static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
189     struct tm *loctime = localtime(&t);
190     char entryTime[64] = {};
191     if (NULL != loctime)
192     {
193         strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
194     }
195     // Insert the ':' into the timezone
196     std::string_view t1(entryTime);
197     std::string_view t2(entryTime);
198     if (t1.size() > 2 && t2.size() > 2)
199     {
200         t1.remove_suffix(2);
201         t2.remove_prefix(t2.size() - 2);
202     }
203     entryTimestamp = std::string(t1) + ":" + std::string(t2);
204     return true;
205 }
206 
207 static bool getSkipParam(crow::Response &res, const crow::Request &req,
208                          long &skip)
209 {
210     char *skipParam = req.urlParams.get("$skip");
211     if (skipParam != nullptr)
212     {
213         char *ptr = nullptr;
214         skip = std::strtol(skipParam, &ptr, 10);
215         if (*skipParam == '\0' || *ptr != '\0')
216         {
217 
218             messages::queryParameterValueTypeError(res, std::string(skipParam),
219                                                    "$skip");
220             return false;
221         }
222         if (skip < 0)
223         {
224 
225             messages::queryParameterOutOfRange(res, std::to_string(skip),
226                                                "$skip", "greater than 0");
227             return false;
228         }
229     }
230     return true;
231 }
232 
233 static constexpr const long maxEntriesPerPage = 1000;
234 static bool getTopParam(crow::Response &res, const crow::Request &req,
235                         long &top)
236 {
237     char *topParam = req.urlParams.get("$top");
238     if (topParam != nullptr)
239     {
240         char *ptr = nullptr;
241         top = std::strtol(topParam, &ptr, 10);
242         if (*topParam == '\0' || *ptr != '\0')
243         {
244             messages::queryParameterValueTypeError(res, std::string(topParam),
245                                                    "$top");
246             return false;
247         }
248         if (top < 1 || top > maxEntriesPerPage)
249         {
250 
251             messages::queryParameterOutOfRange(
252                 res, std::to_string(top), "$top",
253                 "1-" + std::to_string(maxEntriesPerPage));
254             return false;
255         }
256     }
257     return true;
258 }
259 
260 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID,
261                              const bool firstEntry = true)
262 {
263     int ret = 0;
264     static uint64_t prevTs = 0;
265     static int index = 0;
266     if (firstEntry)
267     {
268         prevTs = 0;
269     }
270 
271     // Get the entry timestamp
272     uint64_t curTs = 0;
273     ret = sd_journal_get_realtime_usec(journal, &curTs);
274     if (ret < 0)
275     {
276         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
277                          << strerror(-ret);
278         return false;
279     }
280     // If the timestamp isn't unique, increment the index
281     if (curTs == prevTs)
282     {
283         index++;
284     }
285     else
286     {
287         // Otherwise, reset it
288         index = 0;
289     }
290     // Save the timestamp
291     prevTs = curTs;
292 
293     entryID = std::to_string(curTs);
294     if (index > 0)
295     {
296         entryID += "_" + std::to_string(index);
297     }
298     return true;
299 }
300 
301 static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID,
302                              const bool firstEntry = true)
303 {
304     static uint64_t prevTs = 0;
305     static int index = 0;
306     if (firstEntry)
307     {
308         prevTs = 0;
309     }
310 
311     // Get the entry timestamp
312     uint64_t curTs = 0;
313     std::tm timeStruct = {};
314     std::istringstream entryStream(logEntry);
315     if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
316     {
317         curTs = std::mktime(&timeStruct);
318     }
319     // If the timestamp isn't unique, increment the index
320     if (curTs == prevTs)
321     {
322         index++;
323     }
324     else
325     {
326         // Otherwise, reset it
327         index = 0;
328     }
329     // Save the timestamp
330     prevTs = curTs;
331 
332     entryID = std::to_string(curTs);
333     if (index > 0)
334     {
335         entryID += "_" + std::to_string(index);
336     }
337     return true;
338 }
339 
340 static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
341                                uint64_t &timestamp, uint16_t &index)
342 {
343     if (entryID.empty())
344     {
345         return false;
346     }
347     // Convert the unique ID back to a timestamp to find the entry
348     std::string_view tsStr(entryID);
349 
350     auto underscorePos = tsStr.find("_");
351     if (underscorePos != tsStr.npos)
352     {
353         // Timestamp has an index
354         tsStr.remove_suffix(tsStr.size() - underscorePos);
355         std::string_view indexStr(entryID);
356         indexStr.remove_prefix(underscorePos + 1);
357         std::size_t pos;
358         try
359         {
360             index = std::stoul(std::string(indexStr), &pos);
361         }
362         catch (std::invalid_argument)
363         {
364             messages::resourceMissingAtURI(res, entryID);
365             return false;
366         }
367         catch (std::out_of_range)
368         {
369             messages::resourceMissingAtURI(res, entryID);
370             return false;
371         }
372         if (pos != indexStr.size())
373         {
374             messages::resourceMissingAtURI(res, entryID);
375             return false;
376         }
377     }
378     // Timestamp has no index
379     std::size_t pos;
380     try
381     {
382         timestamp = std::stoull(std::string(tsStr), &pos);
383     }
384     catch (std::invalid_argument)
385     {
386         messages::resourceMissingAtURI(res, entryID);
387         return false;
388     }
389     catch (std::out_of_range)
390     {
391         messages::resourceMissingAtURI(res, entryID);
392         return false;
393     }
394     if (pos != tsStr.size())
395     {
396         messages::resourceMissingAtURI(res, entryID);
397         return false;
398     }
399     return true;
400 }
401 
402 static bool
403     getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles)
404 {
405     static const std::filesystem::path redfishLogDir = "/var/log";
406     static const std::string redfishLogFilename = "redfish";
407 
408     // Loop through the directory looking for redfish log files
409     for (const std::filesystem::directory_entry &dirEnt :
410          std::filesystem::directory_iterator(redfishLogDir))
411     {
412         // If we find a redfish log file, save the path
413         std::string filename = dirEnt.path().filename();
414         if (boost::starts_with(filename, redfishLogFilename))
415         {
416             redfishLogFiles.emplace_back(redfishLogDir / filename);
417         }
418     }
419     // As the log files rotate, they are appended with a ".#" that is higher for
420     // the older logs. Since we don't expect more than 10 log files, we
421     // can just sort the list to get them in order from newest to oldest
422     std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
423 
424     return !redfishLogFiles.empty();
425 }
426 
427 class SystemLogServiceCollection : public Node
428 {
429   public:
430     template <typename CrowApp>
431     SystemLogServiceCollection(CrowApp &app) :
432         Node(app, "/redfish/v1/Systems/system/LogServices/")
433     {
434         entityPrivileges = {
435             {boost::beast::http::verb::get, {{"Login"}}},
436             {boost::beast::http::verb::head, {{"Login"}}},
437             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
438             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
439             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
440             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
441     }
442 
443   private:
444     /**
445      * Functions triggers appropriate requests on DBus
446      */
447     void doGet(crow::Response &res, const crow::Request &req,
448                const std::vector<std::string> &params) override
449     {
450         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
451         // Collections don't include the static data added by SubRoute because
452         // it has a duplicate entry for members
453         asyncResp->res.jsonValue["@odata.type"] =
454             "#LogServiceCollection.LogServiceCollection";
455         asyncResp->res.jsonValue["@odata.context"] =
456             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
457         asyncResp->res.jsonValue["@odata.id"] =
458             "/redfish/v1/Systems/system/LogServices";
459         asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
460         asyncResp->res.jsonValue["Description"] =
461             "Collection of LogServices for this Computer System";
462         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
463         logServiceArray = nlohmann::json::array();
464         logServiceArray.push_back(
465             {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
466 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
467         logServiceArray.push_back(
468             {{"@odata.id",
469               "/redfish/v1/Systems/system/LogServices/Crashdump"}});
470 #endif
471         asyncResp->res.jsonValue["Members@odata.count"] =
472             logServiceArray.size();
473     }
474 };
475 
476 class EventLogService : public Node
477 {
478   public:
479     template <typename CrowApp>
480     EventLogService(CrowApp &app) :
481         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
482     {
483         entityPrivileges = {
484             {boost::beast::http::verb::get, {{"Login"}}},
485             {boost::beast::http::verb::head, {{"Login"}}},
486             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
487             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
488             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
489             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
490     }
491 
492   private:
493     void doGet(crow::Response &res, const crow::Request &req,
494                const std::vector<std::string> &params) override
495     {
496         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
497 
498         asyncResp->res.jsonValue["@odata.id"] =
499             "/redfish/v1/Systems/system/LogServices/EventLog";
500         asyncResp->res.jsonValue["@odata.type"] =
501             "#LogService.v1_1_0.LogService";
502         asyncResp->res.jsonValue["@odata.context"] =
503             "/redfish/v1/$metadata#LogService.LogService";
504         asyncResp->res.jsonValue["Name"] = "Event Log Service";
505         asyncResp->res.jsonValue["Description"] = "System Event Log Service";
506         asyncResp->res.jsonValue["Id"] = "Event Log";
507         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
508         asyncResp->res.jsonValue["Entries"] = {
509             {"@odata.id",
510              "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
511         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
512 
513             {"target", "/redfish/v1/Systems/system/LogServices/EventLog/"
514                        "Actions/LogService.ClearLog"}};
515     }
516 };
517 
518 class EventLogClear : public Node
519 {
520   public:
521     EventLogClear(CrowApp &app) :
522         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
523                   "LogService.ClearLog/")
524     {
525         entityPrivileges = {
526             {boost::beast::http::verb::get, {{"Login"}}},
527             {boost::beast::http::verb::head, {{"Login"}}},
528             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
529             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
530             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
531             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
532     }
533 
534   private:
535     void doPost(crow::Response &res, const crow::Request &req,
536                 const std::vector<std::string> &params) override
537     {
538         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
539 
540         // Clear the EventLog by deleting the log files
541         std::vector<std::filesystem::path> redfishLogFiles;
542         if (getRedfishLogFiles(redfishLogFiles))
543         {
544             for (const std::filesystem::path &file : redfishLogFiles)
545             {
546                 std::error_code ec;
547                 std::filesystem::remove(file, ec);
548             }
549         }
550 
551         // Reload rsyslog so it knows to start new log files
552         crow::connections::systemBus->async_method_call(
553             [asyncResp](const boost::system::error_code ec) {
554                 if (ec)
555                 {
556                     BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec;
557                     messages::internalError(asyncResp->res);
558                     return;
559                 }
560 
561                 messages::success(asyncResp->res);
562             },
563             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
564             "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
565             "replace");
566     }
567 };
568 
569 static int fillEventLogEntryJson(const std::string &logEntryID,
570                                  const std::string logEntry,
571                                  nlohmann::json &logEntryJson)
572 {
573     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
574     // First get the Timestamp
575     size_t space = logEntry.find_first_of(" ");
576     if (space == std::string::npos)
577     {
578         return 1;
579     }
580     std::string timestamp = logEntry.substr(0, space);
581     // Then get the log contents
582     size_t entryStart = logEntry.find_first_not_of(" ", space);
583     if (entryStart == std::string::npos)
584     {
585         return 1;
586     }
587     std::string_view entry(logEntry);
588     entry.remove_prefix(entryStart);
589     // Use split to separate the entry into its fields
590     std::vector<std::string> logEntryFields;
591     boost::split(logEntryFields, entry, boost::is_any_of(","),
592                  boost::token_compress_on);
593     // We need at least a MessageId to be valid
594     if (logEntryFields.size() < 1)
595     {
596         return 1;
597     }
598     std::string &messageID = logEntryFields[0];
599 
600     // Get the Message from the MessageRegistry
601     const message_registries::Message *message =
602         message_registries::getMessage(messageID);
603 
604     std::string msg;
605     std::string severity;
606     if (message != nullptr)
607     {
608         msg = message->message;
609         severity = message->severity;
610     }
611 
612     // Get the MessageArgs from the log if there are any
613     boost::beast::span<std::string> messageArgs;
614     if (logEntryFields.size() > 1)
615     {
616         std::string &messageArgsStart = logEntryFields[1];
617         // If the first string is empty, assume there are no MessageArgs
618         std::size_t messageArgsSize = 0;
619         if (!messageArgsStart.empty())
620         {
621             messageArgsSize = logEntryFields.size() - 1;
622         }
623 
624         messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize);
625 
626         // Fill the MessageArgs into the Message
627         int i = 0;
628         for (const std::string &messageArg : messageArgs)
629         {
630             std::string argStr = "%" + std::to_string(++i);
631             size_t argPos = msg.find(argStr);
632             if (argPos != std::string::npos)
633             {
634                 msg.replace(argPos, argStr.length(), messageArg);
635             }
636         }
637     }
638 
639     // Get the Created time from the timestamp. The log timestamp is in RFC3339
640     // format which matches the Redfish format except for the fractional seconds
641     // between the '.' and the '+', so just remove them.
642     std::size_t dot = timestamp.find_first_of(".");
643     std::size_t plus = timestamp.find_first_of("+");
644     if (dot != std::string::npos && plus != std::string::npos)
645     {
646         timestamp.erase(dot, plus - dot);
647     }
648 
649     // Fill in the log entry with the gathered data
650     logEntryJson = {
651         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
652         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
653         {"@odata.id",
654          "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
655              logEntryID},
656         {"Name", "System Event Log Entry"},
657         {"Id", logEntryID},
658         {"Message", std::move(msg)},
659         {"MessageId", std::move(messageID)},
660         {"MessageArgs", std::move(messageArgs)},
661         {"EntryType", "Event"},
662         {"Severity", std::move(severity)},
663         {"Created", std::move(timestamp)}};
664     return 0;
665 }
666 
667 class JournalEventLogEntryCollection : public Node
668 {
669   public:
670     template <typename CrowApp>
671     JournalEventLogEntryCollection(CrowApp &app) :
672         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
673     {
674         entityPrivileges = {
675             {boost::beast::http::verb::get, {{"Login"}}},
676             {boost::beast::http::verb::head, {{"Login"}}},
677             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
678             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
679             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
680             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
681     }
682 
683   private:
684     void doGet(crow::Response &res, const crow::Request &req,
685                const std::vector<std::string> &params) override
686     {
687         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
688         long skip = 0;
689         long top = maxEntriesPerPage; // Show max entries by default
690         if (!getSkipParam(asyncResp->res, req, skip))
691         {
692             return;
693         }
694         if (!getTopParam(asyncResp->res, req, top))
695         {
696             return;
697         }
698         // Collections don't include the static data added by SubRoute because
699         // it has a duplicate entry for members
700         asyncResp->res.jsonValue["@odata.type"] =
701             "#LogEntryCollection.LogEntryCollection";
702         asyncResp->res.jsonValue["@odata.context"] =
703             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
704         asyncResp->res.jsonValue["@odata.id"] =
705             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
706         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
707         asyncResp->res.jsonValue["Description"] =
708             "Collection of System Event Log Entries";
709 
710         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
711         logEntryArray = nlohmann::json::array();
712         // Go through the log files and create a unique ID for each entry
713         std::vector<std::filesystem::path> redfishLogFiles;
714         getRedfishLogFiles(redfishLogFiles);
715         uint64_t entryCount = 0;
716         std::string logEntry;
717 
718         // Oldest logs are in the last file, so start there and loop backwards
719         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
720              it++)
721         {
722             std::ifstream logStream(*it);
723             if (!logStream.is_open())
724             {
725                 continue;
726             }
727 
728             // Reset the unique ID on the first entry
729             bool firstEntry = true;
730             while (std::getline(logStream, logEntry))
731             {
732                 entryCount++;
733                 // Handle paging using skip (number of entries to skip from the
734                 // start) and top (number of entries to display)
735                 if (entryCount <= skip || entryCount > skip + top)
736                 {
737                     continue;
738                 }
739 
740                 std::string idStr;
741                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
742                 {
743                     continue;
744                 }
745 
746                 if (firstEntry)
747                 {
748                     firstEntry = false;
749                 }
750 
751                 logEntryArray.push_back({});
752                 nlohmann::json &bmcLogEntry = logEntryArray.back();
753                 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
754                 {
755                     messages::internalError(asyncResp->res);
756                     return;
757                 }
758             }
759         }
760         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
761         if (skip + top < entryCount)
762         {
763             asyncResp->res.jsonValue["Members@odata.nextLink"] =
764                 "/redfish/v1/Systems/system/LogServices/EventLog/"
765                 "Entries?$skip=" +
766                 std::to_string(skip + top);
767         }
768     }
769 };
770 
771 class JournalEventLogEntry : public Node
772 {
773   public:
774     JournalEventLogEntry(CrowApp &app) :
775         Node(app,
776              "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
777              std::string())
778     {
779         entityPrivileges = {
780             {boost::beast::http::verb::get, {{"Login"}}},
781             {boost::beast::http::verb::head, {{"Login"}}},
782             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
783             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
784             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
785             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
786     }
787 
788   private:
789     void doGet(crow::Response &res, const crow::Request &req,
790                const std::vector<std::string> &params) override
791     {
792         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
793         if (params.size() != 1)
794         {
795             messages::internalError(asyncResp->res);
796             return;
797         }
798         const std::string &targetID = params[0];
799 
800         // Go through the log files and check the unique ID for each entry to
801         // find the target entry
802         std::vector<std::filesystem::path> redfishLogFiles;
803         getRedfishLogFiles(redfishLogFiles);
804         std::string logEntry;
805 
806         // Oldest logs are in the last file, so start there and loop backwards
807         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
808              it++)
809         {
810             std::ifstream logStream(*it);
811             if (!logStream.is_open())
812             {
813                 continue;
814             }
815 
816             // Reset the unique ID on the first entry
817             bool firstEntry = true;
818             while (std::getline(logStream, logEntry))
819             {
820                 std::string idStr;
821                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
822                 {
823                     continue;
824                 }
825 
826                 if (firstEntry)
827                 {
828                     firstEntry = false;
829                 }
830 
831                 if (idStr == targetID)
832                 {
833                     if (fillEventLogEntryJson(idStr, logEntry,
834                                               asyncResp->res.jsonValue) != 0)
835                     {
836                         messages::internalError(asyncResp->res);
837                         return;
838                     }
839                     return;
840                 }
841             }
842         }
843         // Requested ID was not found
844         messages::resourceMissingAtURI(asyncResp->res, targetID);
845     }
846 };
847 
848 class DBusEventLogEntryCollection : public Node
849 {
850   public:
851     template <typename CrowApp>
852     DBusEventLogEntryCollection(CrowApp &app) :
853         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
854     {
855         entityPrivileges = {
856             {boost::beast::http::verb::get, {{"Login"}}},
857             {boost::beast::http::verb::head, {{"Login"}}},
858             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
859             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
860             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
861             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
862     }
863 
864   private:
865     void doGet(crow::Response &res, const crow::Request &req,
866                const std::vector<std::string> &params) override
867     {
868         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
869 
870         // Collections don't include the static data added by SubRoute because
871         // it has a duplicate entry for members
872         asyncResp->res.jsonValue["@odata.type"] =
873             "#LogEntryCollection.LogEntryCollection";
874         asyncResp->res.jsonValue["@odata.context"] =
875             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
876         asyncResp->res.jsonValue["@odata.id"] =
877             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
878         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
879         asyncResp->res.jsonValue["Description"] =
880             "Collection of System Event Log Entries";
881 
882         // DBus implementation of EventLog/Entries
883         // Make call to Logging Service to find all log entry objects
884         crow::connections::systemBus->async_method_call(
885             [asyncResp](const boost::system::error_code ec,
886                         GetManagedObjectsType &resp) {
887                 if (ec)
888                 {
889                     // TODO Handle for specific error code
890                     BMCWEB_LOG_ERROR
891                         << "getLogEntriesIfaceData resp_handler got error "
892                         << ec;
893                     messages::internalError(asyncResp->res);
894                     return;
895                 }
896                 nlohmann::json &entriesArray =
897                     asyncResp->res.jsonValue["Members"];
898                 entriesArray = nlohmann::json::array();
899                 for (auto &objectPath : resp)
900                 {
901                     for (auto &interfaceMap : objectPath.second)
902                     {
903                         if (interfaceMap.first !=
904                             "xyz.openbmc_project.Logging.Entry")
905                         {
906                             BMCWEB_LOG_DEBUG << "Bailing early on "
907                                              << interfaceMap.first;
908                             continue;
909                         }
910                         entriesArray.push_back({});
911                         nlohmann::json &thisEntry = entriesArray.back();
912                         uint32_t *id;
913                         std::time_t timestamp;
914                         std::string *severity, *message;
915                         bool *resolved;
916                         for (auto &propertyMap : interfaceMap.second)
917                         {
918                             if (propertyMap.first == "Id")
919                             {
920                                 id = sdbusplus::message::variant_ns::get_if<
921                                     uint32_t>(&propertyMap.second);
922                                 if (id == nullptr)
923                                 {
924                                     messages::propertyMissing(asyncResp->res,
925                                                               "Id");
926                                 }
927                             }
928                             else if (propertyMap.first == "Timestamp")
929                             {
930                                 const uint64_t *millisTimeStamp =
931                                     std::get_if<uint64_t>(&propertyMap.second);
932                                 if (millisTimeStamp == nullptr)
933                                 {
934                                     messages::propertyMissing(asyncResp->res,
935                                                               "Timestamp");
936                                 }
937                                 // Retrieve Created property with format:
938                                 // yyyy-mm-ddThh:mm:ss
939                                 std::chrono::milliseconds chronoTimeStamp(
940                                     *millisTimeStamp);
941                                 timestamp =
942                                     std::chrono::duration_cast<
943                                         std::chrono::seconds>(chronoTimeStamp)
944                                         .count();
945                             }
946                             else if (propertyMap.first == "Severity")
947                             {
948                                 severity = std::get_if<std::string>(
949                                     &propertyMap.second);
950                                 if (severity == nullptr)
951                                 {
952                                     messages::propertyMissing(asyncResp->res,
953                                                               "Severity");
954                                 }
955                             }
956                             else if (propertyMap.first == "Message")
957                             {
958                                 message = std::get_if<std::string>(
959                                     &propertyMap.second);
960                                 if (message == nullptr)
961                                 {
962                                     messages::propertyMissing(asyncResp->res,
963                                                               "Message");
964                                 }
965                             }
966                         }
967                         thisEntry = {
968                             {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
969                             {"@odata.context", "/redfish/v1/"
970                                                "$metadata#LogEntry.LogEntry"},
971                             {"@odata.id",
972                              "/redfish/v1/Systems/system/LogServices/EventLog/"
973                              "Entries/" +
974                                  std::to_string(*id)},
975                             {"Name", "System Event Log Entry"},
976                             {"Id", std::to_string(*id)},
977                             {"Message", *message},
978                             {"EntryType", "Event"},
979                             {"Severity",
980                              translateSeverityDbusToRedfish(*severity)},
981                             {"Created", crow::utility::getDateTime(timestamp)}};
982                     }
983                 }
984                 std::sort(entriesArray.begin(), entriesArray.end(),
985                           [](const nlohmann::json &left,
986                              const nlohmann::json &right) {
987                               return (left["Id"] <= right["Id"]);
988                           });
989                 asyncResp->res.jsonValue["Members@odata.count"] =
990                     entriesArray.size();
991             },
992             "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
993             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
994     }
995 };
996 
997 class DBusEventLogEntry : public Node
998 {
999   public:
1000     DBusEventLogEntry(CrowApp &app) :
1001         Node(app,
1002              "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
1003              std::string())
1004     {
1005         entityPrivileges = {
1006             {boost::beast::http::verb::get, {{"Login"}}},
1007             {boost::beast::http::verb::head, {{"Login"}}},
1008             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1009             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1010             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1011             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1012     }
1013 
1014   private:
1015     void doGet(crow::Response &res, const crow::Request &req,
1016                const std::vector<std::string> &params) override
1017     {
1018         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1019         if (params.size() != 1)
1020         {
1021             messages::internalError(asyncResp->res);
1022             return;
1023         }
1024         const std::string &entryID = params[0];
1025 
1026         // DBus implementation of EventLog/Entries
1027         // Make call to Logging Service to find all log entry objects
1028         crow::connections::systemBus->async_method_call(
1029             [asyncResp, entryID](const boost::system::error_code ec,
1030                                  GetManagedPropertyType &resp) {
1031                 if (ec)
1032                 {
1033                     BMCWEB_LOG_ERROR
1034                         << "EventLogEntry (DBus) resp_handler got error " << ec;
1035                     messages::internalError(asyncResp->res);
1036                     return;
1037                 }
1038                 uint32_t *id;
1039                 std::time_t timestamp;
1040                 std::string *severity, *message;
1041                 bool *resolved;
1042                 for (auto &propertyMap : resp)
1043                 {
1044                     if (propertyMap.first == "Id")
1045                     {
1046                         id = std::get_if<uint32_t>(&propertyMap.second);
1047                         if (id == nullptr)
1048                         {
1049                             messages::propertyMissing(asyncResp->res, "Id");
1050                         }
1051                     }
1052                     else if (propertyMap.first == "Timestamp")
1053                     {
1054                         const uint64_t *millisTimeStamp =
1055                             std::get_if<uint64_t>(&propertyMap.second);
1056                         if (millisTimeStamp == nullptr)
1057                         {
1058                             messages::propertyMissing(asyncResp->res,
1059                                                       "Timestamp");
1060                         }
1061                         // Retrieve Created property with format:
1062                         // yyyy-mm-ddThh:mm:ss
1063                         std::chrono::milliseconds chronoTimeStamp(
1064                             *millisTimeStamp);
1065                         timestamp =
1066                             std::chrono::duration_cast<std::chrono::seconds>(
1067                                 chronoTimeStamp)
1068                                 .count();
1069                     }
1070                     else if (propertyMap.first == "Severity")
1071                     {
1072                         severity =
1073                             std::get_if<std::string>(&propertyMap.second);
1074                         if (severity == nullptr)
1075                         {
1076                             messages::propertyMissing(asyncResp->res,
1077                                                       "Severity");
1078                         }
1079                     }
1080                     else if (propertyMap.first == "Message")
1081                     {
1082                         message = std::get_if<std::string>(&propertyMap.second);
1083                         if (message == nullptr)
1084                         {
1085                             messages::propertyMissing(asyncResp->res,
1086                                                       "Message");
1087                         }
1088                     }
1089                 }
1090                 asyncResp->res.jsonValue = {
1091                     {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1092                     {"@odata.context", "/redfish/v1/"
1093                                        "$metadata#LogEntry.LogEntry"},
1094                     {"@odata.id",
1095                      "/redfish/v1/Systems/system/LogServices/EventLog/"
1096                      "Entries/" +
1097                          std::to_string(*id)},
1098                     {"Name", "System Event Log Entry"},
1099                     {"Id", std::to_string(*id)},
1100                     {"Message", *message},
1101                     {"EntryType", "Event"},
1102                     {"Severity", translateSeverityDbusToRedfish(*severity)},
1103                     {"Created", crow::utility::getDateTime(timestamp)}};
1104             },
1105             "xyz.openbmc_project.Logging",
1106             "/xyz/openbmc_project/logging/entry/" + entryID,
1107             "org.freedesktop.DBus.Properties", "GetAll",
1108             "xyz.openbmc_project.Logging.Entry");
1109     }
1110 
1111     void doDelete(crow::Response &res, const crow::Request &req,
1112                   const std::vector<std::string> &params) override
1113     {
1114 
1115         BMCWEB_LOG_DEBUG << "Do delete single event entries.";
1116 
1117         auto asyncResp = std::make_shared<AsyncResp>(res);
1118 
1119         if (params.size() != 1)
1120         {
1121             messages::internalError(asyncResp->res);
1122             return;
1123         }
1124         std::string entryID = params[0];
1125 
1126         dbus::utility::escapePathForDbus(entryID);
1127 
1128         // Process response from Logging service.
1129         auto respHandler = [asyncResp](const boost::system::error_code ec) {
1130             BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
1131             if (ec)
1132             {
1133                 // TODO Handle for specific error code
1134                 BMCWEB_LOG_ERROR
1135                     << "EventLogEntry (DBus) doDelete respHandler got error "
1136                     << ec;
1137                 asyncResp->res.result(
1138                     boost::beast::http::status::internal_server_error);
1139                 return;
1140             }
1141 
1142             asyncResp->res.result(boost::beast::http::status::ok);
1143         };
1144 
1145         // Make call to Logging service to request Delete Log
1146         crow::connections::systemBus->async_method_call(
1147             respHandler, "xyz.openbmc_project.Logging",
1148             "/xyz/openbmc_project/logging/entry/" + entryID,
1149             "xyz.openbmc_project.Object.Delete", "Delete");
1150     }
1151 };
1152 
1153 class BMCLogServiceCollection : public Node
1154 {
1155   public:
1156     template <typename CrowApp>
1157     BMCLogServiceCollection(CrowApp &app) :
1158         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
1159     {
1160         entityPrivileges = {
1161             {boost::beast::http::verb::get, {{"Login"}}},
1162             {boost::beast::http::verb::head, {{"Login"}}},
1163             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1164             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1165             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1166             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1167     }
1168 
1169   private:
1170     /**
1171      * Functions triggers appropriate requests on DBus
1172      */
1173     void doGet(crow::Response &res, const crow::Request &req,
1174                const std::vector<std::string> &params) override
1175     {
1176         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1177         // Collections don't include the static data added by SubRoute because
1178         // it has a duplicate entry for members
1179         asyncResp->res.jsonValue["@odata.type"] =
1180             "#LogServiceCollection.LogServiceCollection";
1181         asyncResp->res.jsonValue["@odata.context"] =
1182             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
1183         asyncResp->res.jsonValue["@odata.id"] =
1184             "/redfish/v1/Managers/bmc/LogServices";
1185         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
1186         asyncResp->res.jsonValue["Description"] =
1187             "Collection of LogServices for this Manager";
1188         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
1189         logServiceArray = nlohmann::json::array();
1190 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
1191         logServiceArray.push_back(
1192             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
1193 #endif
1194         asyncResp->res.jsonValue["Members@odata.count"] =
1195             logServiceArray.size();
1196     }
1197 };
1198 
1199 class BMCJournalLogService : public Node
1200 {
1201   public:
1202     template <typename CrowApp>
1203     BMCJournalLogService(CrowApp &app) :
1204         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
1205     {
1206         entityPrivileges = {
1207             {boost::beast::http::verb::get, {{"Login"}}},
1208             {boost::beast::http::verb::head, {{"Login"}}},
1209             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1210             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1211             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1212             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1213     }
1214 
1215   private:
1216     void doGet(crow::Response &res, const crow::Request &req,
1217                const std::vector<std::string> &params) override
1218     {
1219         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1220         asyncResp->res.jsonValue["@odata.type"] =
1221             "#LogService.v1_1_0.LogService";
1222         asyncResp->res.jsonValue["@odata.id"] =
1223             "/redfish/v1/Managers/bmc/LogServices/Journal";
1224         asyncResp->res.jsonValue["@odata.context"] =
1225             "/redfish/v1/$metadata#LogService.LogService";
1226         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
1227         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
1228         asyncResp->res.jsonValue["Id"] = "BMC Journal";
1229         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1230         asyncResp->res.jsonValue["Entries"] = {
1231             {"@odata.id",
1232              "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}};
1233     }
1234 };
1235 
1236 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
1237                                       sd_journal *journal,
1238                                       nlohmann::json &bmcJournalLogEntryJson)
1239 {
1240     // Get the Log Entry contents
1241     int ret = 0;
1242 
1243     std::string_view msg;
1244     ret = getJournalMetadata(journal, "MESSAGE", msg);
1245     if (ret < 0)
1246     {
1247         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1248         return 1;
1249     }
1250 
1251     // Get the severity from the PRIORITY field
1252     int severity = 8; // Default to an invalid priority
1253     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
1254     if (ret < 0)
1255     {
1256         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
1257     }
1258 
1259     // Get the Created time from the timestamp
1260     std::string entryTimeStr;
1261     if (!getEntryTimestamp(journal, entryTimeStr))
1262     {
1263         return 1;
1264     }
1265 
1266     // Fill in the log entry with the gathered data
1267     bmcJournalLogEntryJson = {
1268         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1269         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1270         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1271                           bmcJournalLogEntryID},
1272         {"Name", "BMC Journal Entry"},
1273         {"Id", bmcJournalLogEntryID},
1274         {"Message", msg},
1275         {"EntryType", "Oem"},
1276         {"Severity",
1277          severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"},
1278         {"OemRecordFormat", "BMC Journal Entry"},
1279         {"Created", std::move(entryTimeStr)}};
1280     return 0;
1281 }
1282 
1283 class BMCJournalLogEntryCollection : public Node
1284 {
1285   public:
1286     template <typename CrowApp>
1287     BMCJournalLogEntryCollection(CrowApp &app) :
1288         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
1289     {
1290         entityPrivileges = {
1291             {boost::beast::http::verb::get, {{"Login"}}},
1292             {boost::beast::http::verb::head, {{"Login"}}},
1293             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1294             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1295             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1296             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1297     }
1298 
1299   private:
1300     void doGet(crow::Response &res, const crow::Request &req,
1301                const std::vector<std::string> &params) override
1302     {
1303         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1304         static constexpr const long maxEntriesPerPage = 1000;
1305         long skip = 0;
1306         long top = maxEntriesPerPage; // Show max entries by default
1307         if (!getSkipParam(asyncResp->res, req, skip))
1308         {
1309             return;
1310         }
1311         if (!getTopParam(asyncResp->res, req, top))
1312         {
1313             return;
1314         }
1315         // Collections don't include the static data added by SubRoute because
1316         // it has a duplicate entry for members
1317         asyncResp->res.jsonValue["@odata.type"] =
1318             "#LogEntryCollection.LogEntryCollection";
1319         asyncResp->res.jsonValue["@odata.id"] =
1320             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1321         asyncResp->res.jsonValue["@odata.context"] =
1322             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
1323         asyncResp->res.jsonValue["@odata.id"] =
1324             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1325         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1326         asyncResp->res.jsonValue["Description"] =
1327             "Collection of BMC Journal Entries";
1328         asyncResp->res.jsonValue["@odata.id"] =
1329             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
1330         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1331         logEntryArray = nlohmann::json::array();
1332 
1333         // Go through the journal and use the timestamp to create a unique ID
1334         // for each entry
1335         sd_journal *journalTmp = nullptr;
1336         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1337         if (ret < 0)
1338         {
1339             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1340             messages::internalError(asyncResp->res);
1341             return;
1342         }
1343         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1344             journalTmp, sd_journal_close);
1345         journalTmp = nullptr;
1346         uint64_t entryCount = 0;
1347         // Reset the unique ID on the first entry
1348         bool firstEntry = true;
1349         SD_JOURNAL_FOREACH(journal.get())
1350         {
1351             entryCount++;
1352             // Handle paging using skip (number of entries to skip from the
1353             // start) and top (number of entries to display)
1354             if (entryCount <= skip || entryCount > skip + top)
1355             {
1356                 continue;
1357             }
1358 
1359             std::string idStr;
1360             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
1361             {
1362                 continue;
1363             }
1364 
1365             if (firstEntry)
1366             {
1367                 firstEntry = false;
1368             }
1369 
1370             logEntryArray.push_back({});
1371             nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
1372             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1373                                            bmcJournalLogEntry) != 0)
1374             {
1375                 messages::internalError(asyncResp->res);
1376                 return;
1377             }
1378         }
1379         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1380         if (skip + top < entryCount)
1381         {
1382             asyncResp->res.jsonValue["Members@odata.nextLink"] =
1383                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
1384                 std::to_string(skip + top);
1385         }
1386     }
1387 };
1388 
1389 class BMCJournalLogEntry : public Node
1390 {
1391   public:
1392     BMCJournalLogEntry(CrowApp &app) :
1393         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
1394              std::string())
1395     {
1396         entityPrivileges = {
1397             {boost::beast::http::verb::get, {{"Login"}}},
1398             {boost::beast::http::verb::head, {{"Login"}}},
1399             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1400             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1401             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1402             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1403     }
1404 
1405   private:
1406     void doGet(crow::Response &res, const crow::Request &req,
1407                const std::vector<std::string> &params) override
1408     {
1409         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1410         if (params.size() != 1)
1411         {
1412             messages::internalError(asyncResp->res);
1413             return;
1414         }
1415         const std::string &entryID = params[0];
1416         // Convert the unique ID back to a timestamp to find the entry
1417         uint64_t ts = 0;
1418         uint16_t index = 0;
1419         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
1420         {
1421             return;
1422         }
1423 
1424         sd_journal *journalTmp = nullptr;
1425         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1426         if (ret < 0)
1427         {
1428             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1429             messages::internalError(asyncResp->res);
1430             return;
1431         }
1432         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1433             journalTmp, sd_journal_close);
1434         journalTmp = nullptr;
1435         // Go to the timestamp in the log and move to the entry at the index
1436         // tracking the unique ID
1437         std::string idStr;
1438         bool firstEntry = true;
1439         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
1440         for (int i = 0; i <= index; i++)
1441         {
1442             sd_journal_next(journal.get());
1443             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
1444             {
1445                 messages::internalError(asyncResp->res);
1446                 return;
1447             }
1448             if (firstEntry)
1449             {
1450                 firstEntry = false;
1451             }
1452         }
1453         // Confirm that the entry ID matches what was requested
1454         if (idStr != entryID)
1455         {
1456             messages::resourceMissingAtURI(asyncResp->res, entryID);
1457             return;
1458         }
1459 
1460         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1461                                        asyncResp->res.jsonValue) != 0)
1462         {
1463             messages::internalError(asyncResp->res);
1464             return;
1465         }
1466     }
1467 };
1468 
1469 class CrashdumpService : public Node
1470 {
1471   public:
1472     template <typename CrowApp>
1473     CrashdumpService(CrowApp &app) :
1474         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
1475     {
1476         entityPrivileges = {
1477             {boost::beast::http::verb::get, {{"Login"}}},
1478             {boost::beast::http::verb::head, {{"Login"}}},
1479             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1480             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1481             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1482             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1483     }
1484 
1485   private:
1486     /**
1487      * Functions triggers appropriate requests on DBus
1488      */
1489     void doGet(crow::Response &res, const crow::Request &req,
1490                const std::vector<std::string> &params) override
1491     {
1492         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1493         // Copy over the static data to include the entries added by SubRoute
1494         asyncResp->res.jsonValue["@odata.id"] =
1495             "/redfish/v1/Systems/system/LogServices/Crashdump";
1496         asyncResp->res.jsonValue["@odata.type"] =
1497             "#LogService.v1_1_0.LogService";
1498         asyncResp->res.jsonValue["@odata.context"] =
1499             "/redfish/v1/$metadata#LogService.LogService";
1500         asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service";
1501         asyncResp->res.jsonValue["Description"] = "Crashdump Service";
1502         asyncResp->res.jsonValue["Id"] = "Crashdump";
1503         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1504         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
1505         asyncResp->res.jsonValue["Entries"] = {
1506             {"@odata.id",
1507              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
1508         asyncResp->res.jsonValue["Actions"] = {
1509             {"Oem",
1510              {{"#Crashdump.OnDemand",
1511                {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1512                            "Actions/Oem/Crashdump.OnDemand"}}}}}};
1513 
1514 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
1515         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
1516             {"#Crashdump.SendRawPeci",
1517              {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1518                          "Actions/Oem/Crashdump.SendRawPeci"}}});
1519 #endif
1520     }
1521 };
1522 
1523 class CrashdumpEntryCollection : public Node
1524 {
1525   public:
1526     template <typename CrowApp>
1527     CrashdumpEntryCollection(CrowApp &app) :
1528         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
1529     {
1530         entityPrivileges = {
1531             {boost::beast::http::verb::get, {{"Login"}}},
1532             {boost::beast::http::verb::head, {{"Login"}}},
1533             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1534             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1535             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1536             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1537     }
1538 
1539   private:
1540     /**
1541      * Functions triggers appropriate requests on DBus
1542      */
1543     void doGet(crow::Response &res, const crow::Request &req,
1544                const std::vector<std::string> &params) override
1545     {
1546         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1547         // Collections don't include the static data added by SubRoute because
1548         // it has a duplicate entry for members
1549         auto getLogEntriesCallback = [asyncResp](
1550                                          const boost::system::error_code ec,
1551                                          const std::vector<std::string> &resp) {
1552             if (ec)
1553             {
1554                 if (ec.value() !=
1555                     boost::system::errc::no_such_file_or_directory)
1556                 {
1557                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1558                                      << ec.message();
1559                     messages::internalError(asyncResp->res);
1560                     return;
1561                 }
1562             }
1563             asyncResp->res.jsonValue["@odata.type"] =
1564                 "#LogEntryCollection.LogEntryCollection";
1565             asyncResp->res.jsonValue["@odata.id"] =
1566                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
1567             asyncResp->res.jsonValue["@odata.context"] =
1568                 "/redfish/v1/"
1569                 "$metadata#LogEntryCollection.LogEntryCollection";
1570             asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
1571             asyncResp->res.jsonValue["Description"] =
1572                 "Collection of Crashdump Entries";
1573             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1574             logEntryArray = nlohmann::json::array();
1575             for (const std::string &objpath : resp)
1576             {
1577                 // Don't list the on-demand log
1578                 if (objpath.compare(CrashdumpOnDemandPath) == 0)
1579                 {
1580                     continue;
1581                 }
1582                 std::size_t lastPos = objpath.rfind("/");
1583                 if (lastPos != std::string::npos)
1584                 {
1585                     logEntryArray.push_back(
1586                         {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
1587                                        "Crashdump/Entries/" +
1588                                            objpath.substr(lastPos + 1)}});
1589                 }
1590             }
1591             asyncResp->res.jsonValue["Members@odata.count"] =
1592                 logEntryArray.size();
1593         };
1594         crow::connections::systemBus->async_method_call(
1595             std::move(getLogEntriesCallback),
1596             "xyz.openbmc_project.ObjectMapper",
1597             "/xyz/openbmc_project/object_mapper",
1598             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
1599             std::array<const char *, 1>{CrashdumpInterface});
1600     }
1601 };
1602 
1603 std::string getLogCreatedTime(const nlohmann::json &Crashdump)
1604 {
1605     nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data");
1606     if (cdIt != Crashdump.end())
1607     {
1608         nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1609         if (siIt != cdIt->end())
1610         {
1611             nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1612             if (tsIt != siIt->end())
1613             {
1614                 const std::string *logTime =
1615                     tsIt->get_ptr<const std::string *>();
1616                 if (logTime != nullptr)
1617                 {
1618                     return *logTime;
1619                 }
1620             }
1621         }
1622     }
1623     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1624 
1625     return std::string();
1626 }
1627 
1628 class CrashdumpEntry : public Node
1629 {
1630   public:
1631     CrashdumpEntry(CrowApp &app) :
1632         Node(app,
1633              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
1634              std::string())
1635     {
1636         entityPrivileges = {
1637             {boost::beast::http::verb::get, {{"Login"}}},
1638             {boost::beast::http::verb::head, {{"Login"}}},
1639             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1640             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1641             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1642             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1643     }
1644 
1645   private:
1646     void doGet(crow::Response &res, const crow::Request &req,
1647                const std::vector<std::string> &params) override
1648     {
1649         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1650         if (params.size() != 1)
1651         {
1652             messages::internalError(asyncResp->res);
1653             return;
1654         }
1655         const uint8_t logId = std::atoi(params[0].c_str());
1656         auto getStoredLogCallback = [asyncResp, logId](
1657                                         const boost::system::error_code ec,
1658                                         const std::variant<std::string> &resp) {
1659             if (ec)
1660             {
1661                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1662                 messages::internalError(asyncResp->res);
1663                 return;
1664             }
1665             const std::string *log = std::get_if<std::string>(&resp);
1666             if (log == nullptr)
1667             {
1668                 messages::internalError(asyncResp->res);
1669                 return;
1670             }
1671             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1672             if (j.is_discarded())
1673             {
1674                 messages::internalError(asyncResp->res);
1675                 return;
1676             }
1677             std::string t = getLogCreatedTime(j);
1678             asyncResp->res.jsonValue = {
1679                 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1680                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1681                 {"@odata.id",
1682                  "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
1683                      std::to_string(logId)},
1684                 {"Name", "CPU Crashdump"},
1685                 {"Id", logId},
1686                 {"EntryType", "Oem"},
1687                 {"OemRecordFormat", "Intel Crashdump"},
1688                 {"Oem", {{"Intel", std::move(j)}}},
1689                 {"Created", std::move(t)}};
1690         };
1691         crow::connections::systemBus->async_method_call(
1692             std::move(getStoredLogCallback), CrashdumpObject,
1693             CrashdumpPath + std::string("/") + std::to_string(logId),
1694             "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface,
1695             "Log");
1696     }
1697 };
1698 
1699 class OnDemandCrashdump : public Node
1700 {
1701   public:
1702     OnDemandCrashdump(CrowApp &app) :
1703         Node(app,
1704              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1705              "Crashdump.OnDemand/")
1706     {
1707         entityPrivileges = {
1708             {boost::beast::http::verb::get, {{"Login"}}},
1709             {boost::beast::http::verb::head, {{"Login"}}},
1710             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1711             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1712             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1713             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1714     }
1715 
1716   private:
1717     void doPost(crow::Response &res, const crow::Request &req,
1718                 const std::vector<std::string> &params) override
1719     {
1720         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1721         static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher;
1722 
1723         // Only allow one OnDemand Log request at a time
1724         if (onDemandLogMatcher != nullptr)
1725         {
1726             asyncResp->res.addHeader("Retry-After", "30");
1727             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
1728             return;
1729         }
1730         // Make this static so it survives outside this method
1731         static boost::asio::deadline_timer timeout(*req.ioService);
1732 
1733         timeout.expires_from_now(boost::posix_time::seconds(30));
1734         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
1735             onDemandLogMatcher = nullptr;
1736             if (ec)
1737             {
1738                 // operation_aborted is expected if timer is canceled before
1739                 // completion.
1740                 if (ec != boost::asio::error::operation_aborted)
1741                 {
1742                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1743                 }
1744                 return;
1745             }
1746             BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log";
1747 
1748             messages::internalError(asyncResp->res);
1749         });
1750 
1751         auto onDemandLogMatcherCallback = [asyncResp](
1752                                               sdbusplus::message::message &m) {
1753             BMCWEB_LOG_DEBUG << "OnDemand log available match fired";
1754             boost::system::error_code ec;
1755             timeout.cancel(ec);
1756             if (ec)
1757             {
1758                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1759             }
1760             sdbusplus::message::object_path objPath;
1761             boost::container::flat_map<
1762                 std::string, boost::container::flat_map<
1763                                  std::string, std::variant<std::string>>>
1764                 interfacesAdded;
1765             m.read(objPath, interfacesAdded);
1766             const std::string *log = std::get_if<std::string>(
1767                 &interfacesAdded[CrashdumpInterface]["Log"]);
1768             if (log == nullptr)
1769             {
1770                 messages::internalError(asyncResp->res);
1771                 // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1772                 // match object inside which this lambda is executing.  Once it
1773                 // is set to nullptr, the match object will be destroyed and the
1774                 // lambda will lose its context, including res, so it needs to
1775                 // be the last thing done.
1776                 onDemandLogMatcher = nullptr;
1777                 return;
1778             }
1779             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1780             if (j.is_discarded())
1781             {
1782                 messages::internalError(asyncResp->res);
1783                 // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1784                 // match object inside which this lambda is executing.  Once it
1785                 // is set to nullptr, the match object will be destroyed and the
1786                 // lambda will lose its context, including res, so it needs to
1787                 // be the last thing done.
1788                 onDemandLogMatcher = nullptr;
1789                 return;
1790             }
1791             std::string t = getLogCreatedTime(j);
1792             asyncResp->res.jsonValue = {
1793                 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1794                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1795                 {"Name", "CPU Crashdump"},
1796                 {"EntryType", "Oem"},
1797                 {"OemRecordFormat", "Intel Crashdump"},
1798                 {"Oem", {{"Intel", std::move(j)}}},
1799                 {"Created", std::move(t)}};
1800             // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1801             // match object inside which this lambda is executing.  Once it is
1802             // set to nullptr, the match object will be destroyed and the lambda
1803             // will lose its context, including res, so it needs to be the last
1804             // thing done.
1805             onDemandLogMatcher = nullptr;
1806         };
1807         onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1808             *crow::connections::systemBus,
1809             sdbusplus::bus::match::rules::interfacesAdded() +
1810                 sdbusplus::bus::match::rules::argNpath(0,
1811                                                        CrashdumpOnDemandPath),
1812             std::move(onDemandLogMatcherCallback));
1813 
1814         auto generateonDemandLogCallback =
1815             [asyncResp](const boost::system::error_code ec,
1816                         const std::string &resp) {
1817                 if (ec)
1818                 {
1819                     if (ec.value() ==
1820                         boost::system::errc::operation_not_supported)
1821                     {
1822                         messages::resourceInStandby(asyncResp->res);
1823                     }
1824                     else
1825                     {
1826                         messages::internalError(asyncResp->res);
1827                     }
1828                     boost::system::error_code timeoutec;
1829                     timeout.cancel(timeoutec);
1830                     if (timeoutec)
1831                     {
1832                         BMCWEB_LOG_ERROR << "error canceling timer "
1833                                          << timeoutec;
1834                     }
1835                     onDemandLogMatcher = nullptr;
1836                     return;
1837                 }
1838             };
1839         crow::connections::systemBus->async_method_call(
1840             std::move(generateonDemandLogCallback), CrashdumpObject,
1841             CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog");
1842     }
1843 };
1844 
1845 class SendRawPECI : public Node
1846 {
1847   public:
1848     SendRawPECI(CrowApp &app) :
1849         Node(app,
1850              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1851              "Crashdump.SendRawPeci/")
1852     {
1853         entityPrivileges = {
1854             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1855             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1856             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1857             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1858             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1859             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1860     }
1861 
1862   private:
1863     void doPost(crow::Response &res, const crow::Request &req,
1864                 const std::vector<std::string> &params) override
1865     {
1866         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1867         uint8_t clientAddress = 0;
1868         uint8_t readLength = 0;
1869         std::vector<uint8_t> peciCommand;
1870         if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1871                                  "ReadLength", readLength, "PECICommand",
1872                                  peciCommand))
1873         {
1874             return;
1875         }
1876 
1877         // Callback to return the Raw PECI response
1878         auto sendRawPECICallback =
1879             [asyncResp](const boost::system::error_code ec,
1880                         const std::vector<uint8_t> &resp) {
1881                 if (ec)
1882                 {
1883                     BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1884                                      << ec.message();
1885                     messages::internalError(asyncResp->res);
1886                     return;
1887                 }
1888                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1889                                             {"PECIResponse", resp}};
1890             };
1891         // Call the SendRawPECI command with the provided data
1892         crow::connections::systemBus->async_method_call(
1893             std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath,
1894             CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength,
1895             peciCommand);
1896     }
1897 };
1898 
1899 /**
1900  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
1901  */
1902 class DBusLogServiceActionsClear : public Node
1903 {
1904   public:
1905     DBusLogServiceActionsClear(CrowApp &app) :
1906         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
1907                   "LogService.Reset")
1908     {
1909         entityPrivileges = {
1910             {boost::beast::http::verb::get, {{"Login"}}},
1911             {boost::beast::http::verb::head, {{"Login"}}},
1912             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1913             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1914             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1915             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1916     }
1917 
1918   private:
1919     /**
1920      * Function handles POST method request.
1921      * The Clear Log actions does not require any parameter.The action deletes
1922      * all entries found in the Entries collection for this Log Service.
1923      */
1924     void doPost(crow::Response &res, const crow::Request &req,
1925                 const std::vector<std::string> &params) override
1926     {
1927         BMCWEB_LOG_DEBUG << "Do delete all entries.";
1928 
1929         auto asyncResp = std::make_shared<AsyncResp>(res);
1930         // Process response from Logging service.
1931         auto resp_handler = [asyncResp](const boost::system::error_code ec) {
1932             BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
1933             if (ec)
1934             {
1935                 // TODO Handle for specific error code
1936                 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
1937                 asyncResp->res.result(
1938                     boost::beast::http::status::internal_server_error);
1939                 return;
1940             }
1941 
1942             asyncResp->res.result(boost::beast::http::status::no_content);
1943         };
1944 
1945         // Make call to Logging service to request Clear Log
1946         crow::connections::systemBus->async_method_call(
1947             resp_handler, "xyz.openbmc_project.Logging",
1948             "/xyz/openbmc_project/logging",
1949             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1950     }
1951 };
1952 } // namespace redfish
1953