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