xref: /openbmc/bmcweb/redfish-core/include/utils/eventlog_utils.hpp (revision b934d565a45e35870df8ac2007060e814f71fdb3)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5 
6 #include "async_resp.hpp"
7 #include "dbus_utility.hpp"
8 #include "error_messages.hpp"
9 #include "generated/enums/log_service.hpp"
10 #include "http_response.hpp"
11 #include "logging.hpp"
12 #include "registries.hpp"
13 #include "str_utility.hpp"
14 #include "utils/dbus_event_log_entry.hpp"
15 #include "utils/etag_utils.hpp"
16 #include "utils/log_services_utils.hpp"
17 #include "utils/query_param.hpp"
18 #include "utils/time_utils.hpp"
19 
20 #include <boost/beast/http/field.hpp>
21 #include <boost/beast/http/status.hpp>
22 #include <boost/beast/http/verb.hpp>
23 #include <boost/system/linux_error.hpp>
24 #include <boost/url/format.hpp>
25 #include <boost/url/url.hpp>
26 #include <sdbusplus/message.hpp>
27 #include <sdbusplus/message/native_types.hpp>
28 #include <sdbusplus/unpack_properties.hpp>
29 
30 #include <algorithm>
31 #include <cstddef>
32 #include <cstdint>
33 #include <cstdio>
34 #include <ctime>
35 #include <fstream>
36 #include <iomanip>
37 #include <memory>
38 #include <optional>
39 #include <sstream>
40 #include <string>
41 #include <string_view>
42 #include <utility>
43 
44 namespace redfish
45 {
46 namespace eventlog_utils
47 {
48 
49 constexpr const char* rfSystemsStr = "Systems";
50 constexpr const char* rfManagersStr = "Managers";
51 
52 enum class LogServiceParentCollection
53 {
54     Systems,
55     Managers
56 };
57 
logServiceParentCollectionToString(LogServiceParentCollection collection)58 inline std::string logServiceParentCollectionToString(
59     LogServiceParentCollection collection)
60 {
61     std::string collectionStr;
62     switch (collection)
63     {
64         case LogServiceParentCollection::Managers:
65             collectionStr = rfManagersStr;
66             break;
67         case LogServiceParentCollection::Systems:
68             collectionStr = rfSystemsStr;
69             break;
70         default:
71             BMCWEB_LOG_ERROR("Unable to stringify bmcweb eventlog location");
72             break;
73     }
74     return collectionStr;
75 }
76 
getMemberIdFromParentCollection(LogServiceParentCollection collection)77 inline std::string_view getMemberIdFromParentCollection(
78     LogServiceParentCollection collection)
79 {
80     std::string_view memberId;
81 
82     switch (collection)
83     {
84         case LogServiceParentCollection::Managers:
85             memberId = BMCWEB_REDFISH_MANAGER_URI_NAME;
86             break;
87         case LogServiceParentCollection::Systems:
88             memberId = BMCWEB_REDFISH_SYSTEM_URI_NAME;
89             break;
90         default:
91             BMCWEB_LOG_ERROR(
92                 "Unable to stringify bmcweb eventlog location childId");
93             break;
94     }
95     return memberId;
96 }
97 
getLogEntryDescriptorFromParentCollection(LogServiceParentCollection collection)98 inline std::string getLogEntryDescriptorFromParentCollection(
99     LogServiceParentCollection collection)
100 {
101     std::string descriptor;
102     switch (collection)
103     {
104         case LogServiceParentCollection::Managers:
105             descriptor = "Manager";
106             break;
107         case LogServiceParentCollection::Systems:
108             descriptor = "System";
109             break;
110         default:
111             BMCWEB_LOG_ERROR("Unable to get Log Entry descriptor");
112             break;
113     }
114     return descriptor;
115 }
116 
handleSystemsAndManagersEventLogServiceGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,LogServiceParentCollection collection)117 inline void handleSystemsAndManagersEventLogServiceGet(
118     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
119     LogServiceParentCollection collection)
120 {
121     const std::string collectionStr =
122         logServiceParentCollectionToString(collection);
123     const std::string_view memberId =
124         getMemberIdFromParentCollection(collection);
125     const std::string logEntryDescriptor =
126         getLogEntryDescriptorFromParentCollection(collection);
127 
128     if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
129     {
130         messages::internalError(asyncResp->res);
131         return;
132     }
133 
134     asyncResp->res.jsonValue["@odata.id"] = std::format(
135         "/redfish/v1/{}/{}/LogServices/EventLog", collectionStr, memberId);
136     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
137     asyncResp->res.jsonValue["Name"] = "Event Log Service";
138     asyncResp->res.jsonValue["Description"] =
139         std::format("{} Event Log Service", logEntryDescriptor);
140     asyncResp->res.jsonValue["Id"] = "EventLog";
141     asyncResp->res.jsonValue["OverWritePolicy"] =
142         log_service::OverWritePolicy::WrapsWhenFull;
143 
144     std::pair<std::string, std::string> redfishDateTimeOffset =
145         redfish::time_utils::getDateTimeOffsetNow();
146 
147     asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
148     asyncResp->res.jsonValue["DateTimeLocalOffset"] =
149         redfishDateTimeOffset.second;
150 
151     asyncResp->res.jsonValue["Entries"]["@odata.id"] =
152         std::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries",
153                     collectionStr, memberId);
154     asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"]
155 
156         = std::format(
157             "/redfish/v1/{}/{}/LogServices/EventLog/Actions/LogService.ClearLog",
158             collectionStr, memberId);
159     etag_utils::setEtagOmitDateTimeHandler(asyncResp);
160 }
161 
162 /*
163  * Journal EventLog utilities
164  * */
165 
getRedfishLogFiles(std::vector<std::filesystem::path> & redfishLogFiles)166 inline bool getRedfishLogFiles(
167     std::vector<std::filesystem::path>& redfishLogFiles)
168 {
169     static const std::filesystem::path redfishLogDir = "/var/log";
170     static const std::string redfishLogFilename = "redfish";
171 
172     // Loop through the directory looking for redfish log files
173     for (const std::filesystem::directory_entry& dirEnt :
174          std::filesystem::directory_iterator(redfishLogDir))
175     {
176         // If we find a redfish log file, save the path
177         std::string filename = dirEnt.path().filename();
178         if (filename.starts_with(redfishLogFilename))
179         {
180             redfishLogFiles.emplace_back(redfishLogDir / filename);
181         }
182     }
183     // As the log files rotate, they are appended with a ".#" that is higher for
184     // the older logs. Since we don't expect more than 10 log files, we
185     // can just sort the list to get them in order from newest to oldest
186     std::ranges::sort(redfishLogFiles);
187 
188     return !redfishLogFiles.empty();
189 }
190 
getUniqueEntryID(const std::string & logEntry,std::string & entryID,const bool firstEntry=true)191 inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
192                              const bool firstEntry = true)
193 {
194     static time_t prevTs = 0;
195     static int index = 0;
196     if (firstEntry)
197     {
198         prevTs = 0;
199     }
200 
201     // Get the entry timestamp
202     std::time_t curTs = 0;
203     std::tm timeStruct = {};
204     std::istringstream entryStream(logEntry);
205     if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
206     {
207         curTs = std::mktime(&timeStruct);
208     }
209     // If the timestamp isn't unique, increment the index
210     if (curTs == prevTs)
211     {
212         index++;
213     }
214     else
215     {
216         // Otherwise, reset it
217         index = 0;
218     }
219     // Save the timestamp
220     prevTs = curTs;
221 
222     entryID = std::to_string(curTs);
223     if (index > 0)
224     {
225         entryID += "_" + std::to_string(index);
226     }
227     return true;
228 }
229 
230 enum class LogParseError
231 {
232     success,
233     parseFailed,
234     messageIdNotInRegistry,
235 };
236 
fillEventLogEntryJson(const std::string & logEntryID,const std::string & logEntry,nlohmann::json::object_t & logEntryJson,const std::string & collectionStr,const std::string_view memberId,const std::string & logEntryDescriptor)237 static LogParseError fillEventLogEntryJson(
238     const std::string& logEntryID, const std::string& logEntry,
239     nlohmann::json::object_t& logEntryJson, const std::string& collectionStr,
240     const std::string_view memberId, const std::string& logEntryDescriptor)
241 {
242     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
243     // First get the Timestamp
244     size_t space = logEntry.find_first_of(' ');
245     if (space == std::string::npos)
246     {
247         return LogParseError::parseFailed;
248     }
249     std::string timestamp = logEntry.substr(0, space);
250     // Then get the log contents
251     size_t entryStart = logEntry.find_first_not_of(' ', space);
252     if (entryStart == std::string::npos)
253     {
254         return LogParseError::parseFailed;
255     }
256     std::string_view entry(logEntry);
257     entry.remove_prefix(entryStart);
258     // Use split to separate the entry into its fields
259     std::vector<std::string> logEntryFields;
260     bmcweb::split(logEntryFields, entry, ',');
261     // We need at least a MessageId to be valid
262     auto logEntryIter = logEntryFields.begin();
263     if (logEntryIter == logEntryFields.end())
264     {
265         return LogParseError::parseFailed;
266     }
267     std::string& messageID = *logEntryIter;
268 
269     std::optional<registries::MessageId> msgComponents =
270         registries::getMessageComponents(messageID);
271     if (!msgComponents)
272     {
273         return LogParseError::parseFailed;
274     }
275 
276     std::optional<registries::RegistryEntryRef> registry =
277         registries::getRegistryFromPrefix(msgComponents->registryName);
278     if (!registry)
279     {
280         return LogParseError::messageIdNotInRegistry;
281     }
282 
283     // Get the Message from the MessageKey and RegistryEntries
284     const registries::Message* message = registries::getMessageFromRegistry(
285         msgComponents->messageKey, registry->get().entries);
286 
287     logEntryIter++;
288     if (message == nullptr)
289     {
290         BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry);
291         return LogParseError::messageIdNotInRegistry;
292     }
293 
294     const unsigned int& versionMajor = registry->get().header.versionMajor;
295     const unsigned int& versionMinor = registry->get().header.versionMinor;
296 
297     std::vector<std::string_view> messageArgs(logEntryIter,
298                                               logEntryFields.end());
299     messageArgs.resize(message->numberOfArgs);
300 
301     std::string msg =
302         redfish::registries::fillMessageArgs(messageArgs, message->message);
303     if (msg.empty())
304     {
305         return LogParseError::parseFailed;
306     }
307 
308     // Get the Created time from the timestamp. The log timestamp is in RFC3339
309     // format which matches the Redfish format except for the
310     // fractional seconds between the '.' and the '+', so just remove them.
311     std::size_t dot = timestamp.find_first_of('.');
312     std::size_t plus = timestamp.find_first_of('+');
313     if (dot != std::string::npos && plus != std::string::npos)
314     {
315         timestamp.erase(dot, plus - dot);
316     }
317 
318     // Fill in the log entry with the gathered data
319     logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
320     logEntryJson["@odata.id"] =
321         boost::urls::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries/{}",
322                             collectionStr, memberId, logEntryID);
323     logEntryJson["Name"] =
324         std::format("{} Event Log Entry", logEntryDescriptor);
325     logEntryJson["Id"] = logEntryID;
326     logEntryJson["Message"] = std::move(msg);
327     logEntryJson["MessageId"] =
328         std::format("{}.{}.{}.{}", msgComponents->registryName, versionMajor,
329                     versionMinor, msgComponents->messageKey);
330     logEntryJson["MessageArgs"] = messageArgs;
331     logEntryJson["EntryType"] = "Event";
332     logEntryJson["Severity"] = message->messageSeverity;
333     logEntryJson["Created"] = std::move(timestamp);
334     return LogParseError::success;
335 }
336 
handleSystemsAndManagersLogServiceEventLogLogEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,query_param::Query & delegatedQuery,LogServiceParentCollection collection)337 inline void handleSystemsAndManagersLogServiceEventLogLogEntryCollection(
338     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
339     query_param::Query& delegatedQuery, LogServiceParentCollection collection)
340 {
341     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
342     size_t skip = delegatedQuery.skip.value_or(0);
343 
344     const std::string collectionStr =
345         logServiceParentCollectionToString(collection);
346     const std::string_view memberId =
347         getMemberIdFromParentCollection(collection);
348     const std::string logEntryDescriptor =
349         getLogEntryDescriptorFromParentCollection(collection);
350 
351     if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
352     {
353         messages::internalError(asyncResp->res);
354         return;
355     }
356 
357     // Collections don't include the static data added by SubRoute
358     // because it has a duplicate entry for members
359     asyncResp->res.jsonValue["@odata.type"] =
360         "#LogEntryCollection.LogEntryCollection";
361     asyncResp->res.jsonValue["@odata.id"] =
362         std::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries",
363                     collectionStr, memberId);
364     asyncResp->res.jsonValue["Name"] =
365         std::format("{} Event Log Entries", logEntryDescriptor);
366     asyncResp->res.jsonValue["Description"] =
367         std::format("Collection of {} Event Log Entries", logEntryDescriptor);
368 
369     nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
370     logEntryArray = nlohmann::json::array();
371     // Go through the log files and create a unique ID for each
372     // entry
373     std::vector<std::filesystem::path> redfishLogFiles;
374     getRedfishLogFiles(redfishLogFiles);
375     uint64_t entryCount = 0;
376     std::string logEntry;
377 
378     // Oldest logs are in the last file, so start there and loop
379     // backwards
380     for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
381     {
382         std::ifstream logStream(*it);
383         if (!logStream.is_open())
384         {
385             continue;
386         }
387 
388         // Reset the unique ID on the first entry
389         bool firstEntry = true;
390         while (std::getline(logStream, logEntry))
391         {
392             std::string idStr;
393             if (!getUniqueEntryID(logEntry, idStr, firstEntry))
394             {
395                 continue;
396             }
397             firstEntry = false;
398 
399             nlohmann::json::object_t bmcLogEntry;
400             LogParseError status = fillEventLogEntryJson(
401                 idStr, logEntry, bmcLogEntry, collectionStr, memberId,
402                 logEntryDescriptor);
403             if (status == LogParseError::messageIdNotInRegistry)
404             {
405                 continue;
406             }
407             if (status != LogParseError::success)
408             {
409                 messages::internalError(asyncResp->res);
410                 return;
411             }
412 
413             entryCount++;
414             // Handle paging using skip (number of entries to skip from the
415             // start) and top (number of entries to display)
416             if (entryCount <= skip || entryCount > skip + top)
417             {
418                 continue;
419             }
420 
421             logEntryArray.emplace_back(std::move(bmcLogEntry));
422         }
423     }
424     asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
425     if (skip + top < entryCount)
426     {
427         asyncResp->res.jsonValue["Members@odata.nextLink"] =
428             boost::urls::format(
429                 "/redfish/v1/{}/{}/LogServices/EventLog/Entries?$skip={}",
430                 collectionStr, memberId, std::to_string(skip + top));
431     }
432 }
433 
handleSystemsAndManagersLogServiceEventLogEntriesGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & param,LogServiceParentCollection collection)434 inline void handleSystemsAndManagersLogServiceEventLogEntriesGet(
435     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
436     const std::string& param, LogServiceParentCollection collection)
437 {
438     const std::string& targetID = param;
439 
440     const std::string collectionStr =
441         logServiceParentCollectionToString(collection);
442     const std::string_view memberId =
443         getMemberIdFromParentCollection(collection);
444     const std::string logEntryDescriptor =
445         getLogEntryDescriptorFromParentCollection(collection);
446 
447     if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
448     {
449         messages::internalError(asyncResp->res);
450         return;
451     }
452 
453     // Go through the log files and check the unique ID for each
454     // entry to find the target entry
455     std::vector<std::filesystem::path> redfishLogFiles;
456     getRedfishLogFiles(redfishLogFiles);
457     std::string logEntry;
458 
459     // Oldest logs are in the last file, so start there and loop
460     // backwards
461     for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
462     {
463         std::ifstream logStream(*it);
464         if (!logStream.is_open())
465         {
466             continue;
467         }
468 
469         // Reset the unique ID on the first entry
470         bool firstEntry = true;
471         while (std::getline(logStream, logEntry))
472         {
473             std::string idStr;
474             if (!getUniqueEntryID(logEntry, idStr, firstEntry))
475             {
476                 continue;
477             }
478             firstEntry = false;
479 
480             if (idStr == targetID)
481             {
482                 nlohmann::json::object_t bmcLogEntry;
483                 LogParseError status = fillEventLogEntryJson(
484                     idStr, logEntry, bmcLogEntry, collectionStr, memberId,
485                     logEntryDescriptor);
486                 if (status != LogParseError::success)
487                 {
488                     messages::internalError(asyncResp->res);
489                     return;
490                 }
491                 asyncResp->res.jsonValue.update(bmcLogEntry);
492                 return;
493             }
494         }
495     }
496     // Requested ID was not found
497     messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
498 }
499 
handleSystemsAndManagersLogServicesEventLogActionsClearPost(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)500 inline void handleSystemsAndManagersLogServicesEventLogActionsClearPost(
501     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
502 {
503     // Clear the EventLog by deleting the log files
504     std::vector<std::filesystem::path> redfishLogFiles;
505     if (getRedfishLogFiles(redfishLogFiles))
506     {
507         for (const std::filesystem::path& file : redfishLogFiles)
508         {
509             std::error_code ec;
510             std::filesystem::remove(file, ec);
511         }
512     }
513 
514     // Reload rsyslog so it knows to start new log files
515     dbus::utility::async_method_call(
516         asyncResp,
517         [asyncResp](const boost::system::error_code& ec) {
518             if (ec)
519             {
520                 BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec);
521                 messages::internalError(asyncResp->res);
522                 return;
523             }
524 
525             messages::success(asyncResp->res);
526         },
527         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
528         "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
529         "replace");
530 }
531 
532 /*
533  * DBus EventLog utilities
534  * */
535 
getProviderNotifyAction(const std::string & notify)536 inline std::optional<bool> getProviderNotifyAction(const std::string& notify)
537 {
538     std::optional<bool> notifyAction;
539     if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify")
540     {
541         notifyAction = true;
542     }
543     else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit")
544     {
545         notifyAction = false;
546     }
547 
548     return notifyAction;
549 }
550 
translateSeverityDbusToRedfish(const std::string & s)551 inline std::string translateSeverityDbusToRedfish(const std::string& s)
552 {
553     if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
554         (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
555         (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
556         (s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
557     {
558         return "Critical";
559     }
560     if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
561         (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
562         (s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
563     {
564         return "OK";
565     }
566     if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
567     {
568         return "Warning";
569     }
570     return "";
571 }
572 
fillEventLogLogEntryFromDbusLogEntry(const DbusEventLogEntry & entry,nlohmann::json & objectToFillOut,const std::string & collectionStr,const std::string_view memberId,const std::string & logEntryDescriptor)573 inline void fillEventLogLogEntryFromDbusLogEntry(
574     const DbusEventLogEntry& entry, nlohmann::json& objectToFillOut,
575     const std::string& collectionStr, const std::string_view memberId,
576     const std::string& logEntryDescriptor)
577 {
578     objectToFillOut["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
579     objectToFillOut["@odata.id"] =
580         boost::urls::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries/{}",
581                             collectionStr, memberId, std::to_string(entry.Id));
582     objectToFillOut["Name"] =
583         std::format("{} Event Log Entry", logEntryDescriptor);
584     objectToFillOut["Id"] = std::to_string(entry.Id);
585     objectToFillOut["Message"] = entry.Message;
586     objectToFillOut["Resolved"] = entry.Resolved;
587     std::optional<bool> notifyAction =
588         getProviderNotifyAction(entry.ServiceProviderNotify);
589     if (notifyAction)
590     {
591         objectToFillOut["ServiceProviderNotified"] = *notifyAction;
592     }
593     if ((entry.Resolution != nullptr) && !entry.Resolution->empty())
594     {
595         objectToFillOut["Resolution"] = *entry.Resolution;
596     }
597     objectToFillOut["EntryType"] = "Event";
598     objectToFillOut["Severity"] =
599         translateSeverityDbusToRedfish(entry.Severity);
600     objectToFillOut["Created"] =
601         redfish::time_utils::getDateTimeUintMs(entry.Timestamp);
602     objectToFillOut["Modified"] =
603         redfish::time_utils::getDateTimeUintMs(entry.UpdateTimestamp);
604     if (entry.Path != nullptr)
605     {
606         objectToFillOut["AdditionalDataURI"] = boost::urls::format(
607             "/redfish/v1/{}/{}/LogServices/EventLog/Entries/{}/attachment",
608             collectionStr, memberId, std::to_string(entry.Id));
609     }
610 }
611 
afterLogEntriesGetManagedObjects(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & collectionStr,const std::string_view memberId,const std::string & logEntryDescriptor,const boost::system::error_code & ec,const dbus::utility::ManagedObjectType & resp)612 inline void afterLogEntriesGetManagedObjects(
613     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
614     const std::string& collectionStr, const std::string_view memberId,
615     const std::string& logEntryDescriptor, const boost::system::error_code& ec,
616     const dbus::utility::ManagedObjectType& resp)
617 {
618     if (ec)
619     {
620         // TODO Handle for specific error code
621         BMCWEB_LOG_ERROR("getLogEntriesIfaceData resp_handler got error {}",
622                          ec);
623         messages::internalError(asyncResp->res);
624         return;
625     }
626     nlohmann::json::array_t entriesArray;
627     for (const auto& objectPath : resp)
628     {
629         dbus::utility::DBusPropertiesMap propsFlattened;
630         auto isEntry =
631             std::ranges::find_if(objectPath.second, [](const auto& object) {
632                 return object.first == "xyz.openbmc_project.Logging.Entry";
633             });
634         if (isEntry == objectPath.second.end())
635         {
636             continue;
637         }
638 
639         for (const auto& interfaceMap : objectPath.second)
640         {
641             for (const auto& propertyMap : interfaceMap.second)
642             {
643                 propsFlattened.emplace_back(propertyMap.first,
644                                             propertyMap.second);
645             }
646         }
647         std::optional<DbusEventLogEntry> optEntry =
648             fillDbusEventLogEntryFromPropertyMap(propsFlattened);
649 
650         if (!optEntry.has_value())
651         {
652             messages::internalError(asyncResp->res);
653             return;
654         }
655         fillEventLogLogEntryFromDbusLogEntry(
656             *optEntry, entriesArray.emplace_back(), collectionStr, memberId,
657             logEntryDescriptor);
658     }
659 
660     redfish::json_util::sortJsonArrayByKey(entriesArray, "Id");
661     asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
662     asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
663 }
664 
dBusEventLogEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,LogServiceParentCollection collection)665 inline void dBusEventLogEntryCollection(
666     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
667     LogServiceParentCollection collection)
668 {
669     const std::string_view memberId =
670         getMemberIdFromParentCollection(collection);
671     const std::string collectionStr =
672         logServiceParentCollectionToString(collection);
673     const std::string logEntryDescriptor =
674         getLogEntryDescriptorFromParentCollection(collection);
675 
676     if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
677     {
678         messages::internalError(asyncResp->res);
679         return;
680     }
681 
682     // Collections don't include the static data added by SubRoute
683     // because it has a duplicate entry for members
684     asyncResp->res.jsonValue["@odata.type"] =
685         "#LogEntryCollection.LogEntryCollection";
686     asyncResp->res.jsonValue["@odata.id"] =
687         std::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries",
688                     collectionStr, memberId);
689     asyncResp->res.jsonValue["Name"] =
690         std::format("{} Event Log Entries", logEntryDescriptor);
691     asyncResp->res.jsonValue["Description"] =
692         std::format("Collection of {} Event Log Entries", logEntryDescriptor);
693 
694     // DBus implementation of EventLog/Entries
695     // Make call to Logging Service to find all log entry objects
696     sdbusplus::message::object_path path("/xyz/openbmc_project/logging");
697     dbus::utility::getManagedObjects(
698         "xyz.openbmc_project.Logging", path,
699         [asyncResp, collectionStr, memberId,
700          logEntryDescriptor](const boost::system::error_code& ec,
701                              const dbus::utility::ManagedObjectType& resp) {
702             afterLogEntriesGetManagedObjects(asyncResp, collectionStr, memberId,
703                                              logEntryDescriptor, ec, resp);
704         });
705 }
706 
afterDBusEventLogEntryGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & collectionStr,const std::string_view memberId,const std::string & logEntryDescriptor,const std::string & entryID,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & resp)707 inline void afterDBusEventLogEntryGet(
708     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
709     const std::string& collectionStr, const std::string_view memberId,
710     const std::string& logEntryDescriptor, const std::string& entryID,
711     const boost::system::error_code& ec,
712     const dbus::utility::DBusPropertiesMap& resp)
713 {
714     if (ec.value() == EBADR)
715     {
716         messages::resourceNotFound(asyncResp->res, "EventLogEntry", entryID);
717         return;
718     }
719     if (ec)
720     {
721         BMCWEB_LOG_ERROR("EventLogEntry (DBus) resp_handler got error {}", ec);
722         messages::internalError(asyncResp->res);
723         return;
724     }
725 
726     std::optional<DbusEventLogEntry> optEntry =
727         fillDbusEventLogEntryFromPropertyMap(resp);
728 
729     if (!optEntry.has_value())
730     {
731         messages::internalError(asyncResp->res);
732         return;
733     }
734 
735     fillEventLogLogEntryFromDbusLogEntry(
736         *optEntry, asyncResp->res.jsonValue, collectionStr, memberId,
737         logEntryDescriptor);
738 }
739 
dBusEventLogEntryGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,LogServiceParentCollection collection,std::string entryID)740 inline void dBusEventLogEntryGet(
741     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
742     LogServiceParentCollection collection, std::string entryID)
743 {
744     const std::string collectionStr =
745         logServiceParentCollectionToString(collection);
746     const std::string_view memberId =
747         getMemberIdFromParentCollection(collection);
748     const std::string logEntryDescriptor =
749         getLogEntryDescriptorFromParentCollection(collection);
750 
751     if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
752     {
753         messages::internalError(asyncResp->res);
754         return;
755     }
756 
757     dbus::utility::escapePathForDbus(entryID);
758 
759     // DBus implementation of EventLog/Entries
760     // Make call to Logging Service to find all log entry objects
761     dbus::utility::getAllProperties(
762         "xyz.openbmc_project.Logging",
763         "/xyz/openbmc_project/logging/entry/" + entryID, "",
764         std::bind_front(afterDBusEventLogEntryGet, asyncResp, collectionStr,
765                         memberId, logEntryDescriptor, entryID));
766 }
767 
dBusEventLogEntryPatch(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryId)768 inline void dBusEventLogEntryPatch(
769     const crow::Request& req,
770     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
771     const std::string& entryId)
772 {
773     std::optional<bool> resolved;
774 
775     if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", resolved))
776     {
777         return;
778     }
779     BMCWEB_LOG_DEBUG("Set Resolved");
780 
781     setDbusProperty(asyncResp, "Resolved", "xyz.openbmc_project.Logging",
782                     "/xyz/openbmc_project/logging/entry/" + entryId,
783                     "xyz.openbmc_project.Logging.Entry", "Resolved",
784                     resolved.value_or(false));
785 }
786 
dBusEventLogEntryDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string entryID)787 inline void dBusEventLogEntryDelete(
788     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
789 {
790     BMCWEB_LOG_DEBUG("Do delete single event entries.");
791 
792     dbus::utility::escapePathForDbus(entryID);
793 
794     // Process response from Logging service.
795     auto respHandler = [asyncResp,
796                         entryID](const boost::system::error_code& ec) {
797         BMCWEB_LOG_DEBUG("EventLogEntry (DBus) doDelete callback: Done");
798         if (ec)
799         {
800             if (ec.value() == EBADR)
801             {
802                 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
803                 return;
804             }
805             // TODO Handle for specific error code
806             BMCWEB_LOG_ERROR(
807                 "EventLogEntry (DBus) doDelete respHandler got error {}", ec);
808             asyncResp->res.result(
809                 boost::beast::http::status::internal_server_error);
810             return;
811         }
812 
813         messages::success(asyncResp->res);
814     };
815 
816     // Make call to Logging service to request Delete Log
817     dbus::utility::async_method_call(
818         asyncResp, respHandler, "xyz.openbmc_project.Logging",
819         "/xyz/openbmc_project/logging/entry/" + entryID,
820         "xyz.openbmc_project.Object.Delete", "Delete");
821 }
822 
dBusLogServiceActionsClear(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)823 inline void dBusLogServiceActionsClear(
824     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
825 {
826     BMCWEB_LOG_DEBUG("Do delete all entries.");
827 
828     // Process response from Logging service.
829     auto respHandler = [asyncResp](const boost::system::error_code& ec) {
830         BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done");
831         if (ec)
832         {
833             // TODO Handle for specific error code
834             BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec);
835             asyncResp->res.result(
836                 boost::beast::http::status::internal_server_error);
837             return;
838         }
839 
840         messages::success(asyncResp->res);
841     };
842 
843     // Make call to Logging service to request Clear Log
844     dbus::utility::async_method_call(
845         asyncResp, respHandler, "xyz.openbmc_project.Logging",
846         "/xyz/openbmc_project/logging",
847         "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
848 }
849 
downloadEventLogEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & downloadEntryType)850 inline void downloadEventLogEntry(
851     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
852     const std::string& entryID, const std::string& downloadEntryType)
853 {
854     std::string entryPath =
855         sdbusplus::message::object_path("/xyz/openbmc_project/logging/entry") /
856         entryID;
857 
858     auto downloadEventLogEntryHandler =
859         [asyncResp, entryID,
860          downloadEntryType](const boost::system::error_code& ec,
861                             const sdbusplus::message::unix_fd& unixfd) {
862             log_services_utils::downloadEntryCallback(
863                 asyncResp, entryID, downloadEntryType, ec, unixfd);
864         };
865 
866     dbus::utility::async_method_call(
867         asyncResp, std::move(downloadEventLogEntryHandler),
868         "xyz.openbmc_project.Logging", entryPath,
869         "xyz.openbmc_project.Logging.Entry", "GetEntry");
870 }
871 } // namespace eventlog_utils
872 } // namespace redfish
873