xref: /openbmc/bmcweb/redfish-core/lib/systems_logservices_hostlogger.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "app.hpp"
6 #include "generated/enums/log_entry.hpp"
7 #include "query.hpp"
8 #include "registries/openbmc_message_registry.hpp"
9 #include "registries/privilege_registry.hpp"
10 #include "utils/time_utils.hpp"
11 
12 #include <cstdint>
13 #include <memory>
14 #include <string_view>
15 #include <utility>
16 #include <vector>
17 
18 namespace redfish
19 {
20 
21 inline void fillHostLoggerEntryJson(std::string_view logEntryID,
22                                     std::string_view msg,
23                                     nlohmann::json::object_t& logEntryJson)
24 {
25     // Fill in the log entry with the gathered data.
26     logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
27     logEntryJson["@odata.id"] = boost::urls::format(
28         "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries/{}",
29         BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
30     logEntryJson["Name"] = "Host Logger Entry";
31     logEntryJson["Id"] = logEntryID;
32     logEntryJson["Message"] = msg;
33     logEntryJson["EntryType"] = log_entry::LogEntryType::Oem;
34     logEntryJson["Severity"] = log_entry::EventSeverity::OK;
35     logEntryJson["OemRecordFormat"] = "Host Logger Entry";
36 }
37 
38 inline void handleSystemsLogServicesHostloggerGet(
39     App& app, const crow::Request& req,
40     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
41     const std::string& systemName)
42 {
43     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
44     {
45         return;
46     }
47     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
48     {
49         // Option currently returns no systems.  TBD
50         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
51                                    systemName);
52         return;
53     }
54     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
55     {
56         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
57                                    systemName);
58         return;
59     }
60     asyncResp->res.jsonValue["@odata.id"] =
61         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger",
62                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
63     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
64     asyncResp->res.jsonValue["Name"] = "Host Logger Service";
65     asyncResp->res.jsonValue["Description"] = "Host Logger Service";
66     asyncResp->res.jsonValue["Id"] = "HostLogger";
67     asyncResp->res.jsonValue["Entries"]["@odata.id"] =
68         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
69                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
70 }
71 
72 inline void handleSystemsLogServicesHostloggerEntriesGet(
73     App& app, const crow::Request& req,
74     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
75     const std::string& systemName)
76 {
77     query_param::QueryCapabilities capabilities = {
78         .canDelegateTop = true,
79         .canDelegateSkip = true,
80     };
81     query_param::Query delegatedQuery;
82     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
83                                                   delegatedQuery, capabilities))
84     {
85         return;
86     }
87     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
88     {
89         // Option currently returns no systems.  TBD
90         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
91                                    systemName);
92         return;
93     }
94     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
95     {
96         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
97                                    systemName);
98         return;
99     }
100     asyncResp->res.jsonValue["@odata.id"] =
101         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
102                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
103     asyncResp->res.jsonValue["@odata.type"] =
104         "#LogEntryCollection.LogEntryCollection";
105     asyncResp->res.jsonValue["Name"] = "HostLogger Entries";
106     asyncResp->res.jsonValue["Description"] =
107         "Collection of HostLogger Entries";
108     nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
109     logEntryArray = nlohmann::json::array();
110     asyncResp->res.jsonValue["Members@odata.count"] = 0;
111 
112     std::vector<std::filesystem::path> hostLoggerFiles;
113     if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
114     {
115         BMCWEB_LOG_DEBUG("Failed to get host log file path");
116         return;
117     }
118     // If we weren't provided top and skip limits, use the defaults.
119     size_t skip = delegatedQuery.skip.value_or(0);
120     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
121     size_t logCount = 0;
122     // This vector only store the entries we want to expose that
123     // control by skip and top.
124     std::vector<std::string> logEntries;
125     if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries, logCount))
126     {
127         messages::internalError(asyncResp->res);
128         return;
129     }
130     // If vector is empty, that means skip value larger than total
131     // log count
132     if (logEntries.empty())
133     {
134         asyncResp->res.jsonValue["Members@odata.count"] = logCount;
135         return;
136     }
137     if (!logEntries.empty())
138     {
139         for (size_t i = 0; i < logEntries.size(); i++)
140         {
141             nlohmann::json::object_t hostLogEntry;
142             fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i],
143                                     hostLogEntry);
144             logEntryArray.emplace_back(std::move(hostLogEntry));
145         }
146 
147         asyncResp->res.jsonValue["Members@odata.count"] = logCount;
148         if (skip + top < logCount)
149         {
150             asyncResp->res.jsonValue["Members@odata.nextLink"] =
151                 std::format(
152                     "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries?$skip=",
153                     BMCWEB_REDFISH_SYSTEM_URI_NAME) +
154                 std::to_string(skip + top);
155         }
156     }
157 }
158 
159 inline void handleSystemsLogServicesHostloggerEntriesEntryGet(
160     App& app, const crow::Request& req,
161     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
162     const std::string& systemName, const std::string& param)
163 {
164     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
165     {
166         return;
167     }
168     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
169     {
170         // Option currently returns no systems.  TBD
171         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
172                                    systemName);
173         return;
174     }
175     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
176     {
177         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
178                                    systemName);
179         return;
180     }
181     std::string_view targetID = param;
182 
183     uint64_t idInt = 0;
184 
185     auto [ptr, ec] = std::from_chars(targetID.begin(), targetID.end(), idInt);
186     if (ec != std::errc{} || ptr != targetID.end())
187     {
188         messages::resourceNotFound(asyncResp->res, "LogEntry", param);
189         return;
190     }
191 
192     std::vector<std::filesystem::path> hostLoggerFiles;
193     if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
194     {
195         BMCWEB_LOG_DEBUG("Failed to get host log file path");
196         return;
197     }
198 
199     size_t logCount = 0;
200     size_t top = 1;
201     std::vector<std::string> logEntries;
202     // We can get specific entry by skip and top. For example, if we
203     // want to get nth entry, we can set skip = n-1 and top = 1 to
204     // get that entry
205     if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries,
206                               logCount))
207     {
208         messages::internalError(asyncResp->res);
209         return;
210     }
211 
212     if (!logEntries.empty())
213     {
214         nlohmann::json::object_t hostLogEntry;
215         fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry);
216         asyncResp->res.jsonValue.update(hostLogEntry);
217         return;
218     }
219 
220     // Requested ID was not found
221     messages::resourceNotFound(asyncResp->res, "LogEntry", param);
222 }
223 
224 inline void requestRoutesSystemsLogServiceHostlogger(App& app)
225 {
226     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/")
227         .privileges(redfish::privileges::getLogService)
228         .methods(boost::beast::http::verb::get)(std::bind_front(
229             handleSystemsLogServicesHostloggerGet, std::ref(app)));
230     BMCWEB_ROUTE(app,
231                  "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/")
232         .privileges(redfish::privileges::getLogEntryCollection)
233         .methods(boost::beast::http::verb::get)(std::bind_front(
234             handleSystemsLogServicesHostloggerEntriesGet, std::ref(app)));
235 
236     BMCWEB_ROUTE(
237         app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/")
238         .privileges(redfish::privileges::getLogEntry)
239         .methods(boost::beast::http::verb::get)(std::bind_front(
240             handleSystemsLogServicesHostloggerEntriesEntryGet, std::ref(app)));
241 }
242 
243 } // namespace redfish
244