xref: /openbmc/bmcweb/features/redfish/lib/systems_logservices_hostlogger.hpp (revision 3d66430a19af19df9669ccad106af292eec0cdd7)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3e21126ecSEd Tanous #pragma once
4e21126ecSEd Tanous 
5e21126ecSEd Tanous #include "app.hpp"
6e21126ecSEd Tanous #include "generated/enums/log_entry.hpp"
7*3d66430aSEd Tanous #include "gzfile.hpp"
8*3d66430aSEd Tanous #include "human_sort.hpp""
9e21126ecSEd Tanous #include "query.hpp"
10e21126ecSEd Tanous #include "registries/openbmc_message_registry.hpp"
11e21126ecSEd Tanous #include "registries/privilege_registry.hpp"
12e21126ecSEd Tanous #include "utils/time_utils.hpp"
13e21126ecSEd Tanous 
14e21126ecSEd Tanous #include <cstdint>
15e21126ecSEd Tanous #include <memory>
16e21126ecSEd Tanous #include <string_view>
17e21126ecSEd Tanous #include <utility>
18e21126ecSEd Tanous #include <vector>
19e21126ecSEd Tanous 
20e21126ecSEd Tanous namespace redfish
21e21126ecSEd Tanous {
22*3d66430aSEd Tanous constexpr const char* hostLoggerFolderPath = "/var/log/console";
23*3d66430aSEd Tanous 
24*3d66430aSEd Tanous inline bool
25*3d66430aSEd Tanous     getHostLoggerFiles(const std::string& hostLoggerFilePath,
26*3d66430aSEd Tanous                        std::vector<std::filesystem::path>& hostLoggerFiles)
27*3d66430aSEd Tanous {
28*3d66430aSEd Tanous     std::error_code ec;
29*3d66430aSEd Tanous     std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec);
30*3d66430aSEd Tanous     if (ec)
31*3d66430aSEd Tanous     {
32*3d66430aSEd Tanous         BMCWEB_LOG_WARNING("{}", ec.message());
33*3d66430aSEd Tanous         return false;
34*3d66430aSEd Tanous     }
35*3d66430aSEd Tanous     for (const std::filesystem::directory_entry& it : logPath)
36*3d66430aSEd Tanous     {
37*3d66430aSEd Tanous         std::string filename = it.path().filename();
38*3d66430aSEd Tanous         // Prefix of each log files is "log". Find the file and save the
39*3d66430aSEd Tanous         // path
40*3d66430aSEd Tanous         if (filename.starts_with("log"))
41*3d66430aSEd Tanous         {
42*3d66430aSEd Tanous             hostLoggerFiles.emplace_back(it.path());
43*3d66430aSEd Tanous         }
44*3d66430aSEd Tanous     }
45*3d66430aSEd Tanous     // As the log files rotate, they are appended with a ".#" that is higher for
46*3d66430aSEd Tanous     // the older logs. Since we start from oldest logs, sort the name in
47*3d66430aSEd Tanous     // descending order.
48*3d66430aSEd Tanous     std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(),
49*3d66430aSEd Tanous               AlphanumLess<std::string>());
50*3d66430aSEd Tanous 
51*3d66430aSEd Tanous     return true;
52*3d66430aSEd Tanous }
53*3d66430aSEd Tanous 
54*3d66430aSEd Tanous inline bool getHostLoggerEntries(
55*3d66430aSEd Tanous     const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip,
56*3d66430aSEd Tanous     uint64_t top, std::vector<std::string>& logEntries, size_t& logCount)
57*3d66430aSEd Tanous {
58*3d66430aSEd Tanous     GzFileReader logFile;
59*3d66430aSEd Tanous 
60*3d66430aSEd Tanous     // Go though all log files and expose host logs.
61*3d66430aSEd Tanous     for (const std::filesystem::path& it : hostLoggerFiles)
62*3d66430aSEd Tanous     {
63*3d66430aSEd Tanous         if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount))
64*3d66430aSEd Tanous         {
65*3d66430aSEd Tanous             BMCWEB_LOG_ERROR("fail to expose host logs");
66*3d66430aSEd Tanous             return false;
67*3d66430aSEd Tanous         }
68*3d66430aSEd Tanous     }
69*3d66430aSEd Tanous     // Get lastMessage from constructor by getter
70*3d66430aSEd Tanous     std::string lastMessage = logFile.getLastMessage();
71*3d66430aSEd Tanous     if (!lastMessage.empty())
72*3d66430aSEd Tanous     {
73*3d66430aSEd Tanous         logCount++;
74*3d66430aSEd Tanous         if (logCount > skip && logCount <= (skip + top))
75*3d66430aSEd Tanous         {
76*3d66430aSEd Tanous             logEntries.push_back(lastMessage);
77*3d66430aSEd Tanous         }
78*3d66430aSEd Tanous     }
79*3d66430aSEd Tanous     return true;
80*3d66430aSEd Tanous }
81e21126ecSEd Tanous 
82e21126ecSEd Tanous inline void fillHostLoggerEntryJson(std::string_view logEntryID,
83e21126ecSEd Tanous                                     std::string_view msg,
84e21126ecSEd Tanous                                     nlohmann::json::object_t& logEntryJson)
85e21126ecSEd Tanous {
86e21126ecSEd Tanous     // Fill in the log entry with the gathered data.
87e21126ecSEd Tanous     logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
88e21126ecSEd Tanous     logEntryJson["@odata.id"] = boost::urls::format(
89e21126ecSEd Tanous         "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries/{}",
90e21126ecSEd Tanous         BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
91e21126ecSEd Tanous     logEntryJson["Name"] = "Host Logger Entry";
92e21126ecSEd Tanous     logEntryJson["Id"] = logEntryID;
93e21126ecSEd Tanous     logEntryJson["Message"] = msg;
94e21126ecSEd Tanous     logEntryJson["EntryType"] = log_entry::LogEntryType::Oem;
95e21126ecSEd Tanous     logEntryJson["Severity"] = log_entry::EventSeverity::OK;
96e21126ecSEd Tanous     logEntryJson["OemRecordFormat"] = "Host Logger Entry";
97e21126ecSEd Tanous }
98e21126ecSEd Tanous 
997945eeedSEd Tanous inline void handleSystemsLogServicesHostloggerGet(
1007945eeedSEd Tanous     App& app, const crow::Request& req,
101e21126ecSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1027945eeedSEd Tanous     const std::string& systemName)
1037945eeedSEd Tanous {
104e21126ecSEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
105e21126ecSEd Tanous     {
106e21126ecSEd Tanous         return;
107e21126ecSEd Tanous     }
108e21126ecSEd Tanous     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
109e21126ecSEd Tanous     {
110e21126ecSEd Tanous         // Option currently returns no systems.  TBD
111e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
112e21126ecSEd Tanous                                    systemName);
113e21126ecSEd Tanous         return;
114e21126ecSEd Tanous     }
115e21126ecSEd Tanous     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
116e21126ecSEd Tanous     {
117e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
118e21126ecSEd Tanous                                    systemName);
119e21126ecSEd Tanous         return;
120e21126ecSEd Tanous     }
121e21126ecSEd Tanous     asyncResp->res.jsonValue["@odata.id"] =
122e21126ecSEd Tanous         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger",
123e21126ecSEd Tanous                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
1247945eeedSEd Tanous     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
125e21126ecSEd Tanous     asyncResp->res.jsonValue["Name"] = "Host Logger Service";
126e21126ecSEd Tanous     asyncResp->res.jsonValue["Description"] = "Host Logger Service";
127e21126ecSEd Tanous     asyncResp->res.jsonValue["Id"] = "HostLogger";
1287945eeedSEd Tanous     asyncResp->res.jsonValue["Entries"]["@odata.id"] =
1297945eeedSEd Tanous         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
130e21126ecSEd Tanous                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
131e21126ecSEd Tanous }
132e21126ecSEd Tanous 
1337945eeedSEd Tanous inline void handleSystemsLogServicesHostloggerEntriesGet(
1347945eeedSEd Tanous     App& app, const crow::Request& req,
135e21126ecSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1367945eeedSEd Tanous     const std::string& systemName)
1377945eeedSEd Tanous {
138e21126ecSEd Tanous     query_param::QueryCapabilities capabilities = {
139e21126ecSEd Tanous         .canDelegateTop = true,
140e21126ecSEd Tanous         .canDelegateSkip = true,
141e21126ecSEd Tanous     };
142e21126ecSEd Tanous     query_param::Query delegatedQuery;
1437945eeedSEd Tanous     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
1447945eeedSEd Tanous                                                   delegatedQuery, capabilities))
145e21126ecSEd Tanous     {
146e21126ecSEd Tanous         return;
147e21126ecSEd Tanous     }
148e21126ecSEd Tanous     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
149e21126ecSEd Tanous     {
150e21126ecSEd Tanous         // Option currently returns no systems.  TBD
151e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
152e21126ecSEd Tanous                                    systemName);
153e21126ecSEd Tanous         return;
154e21126ecSEd Tanous     }
155e21126ecSEd Tanous     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
156e21126ecSEd Tanous     {
157e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
158e21126ecSEd Tanous                                    systemName);
159e21126ecSEd Tanous         return;
160e21126ecSEd Tanous     }
1617945eeedSEd Tanous     asyncResp->res.jsonValue["@odata.id"] =
1627945eeedSEd Tanous         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
163e21126ecSEd Tanous                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
164e21126ecSEd Tanous     asyncResp->res.jsonValue["@odata.type"] =
165e21126ecSEd Tanous         "#LogEntryCollection.LogEntryCollection";
166e21126ecSEd Tanous     asyncResp->res.jsonValue["Name"] = "HostLogger Entries";
167e21126ecSEd Tanous     asyncResp->res.jsonValue["Description"] =
168e21126ecSEd Tanous         "Collection of HostLogger Entries";
169e21126ecSEd Tanous     nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
170e21126ecSEd Tanous     logEntryArray = nlohmann::json::array();
171e21126ecSEd Tanous     asyncResp->res.jsonValue["Members@odata.count"] = 0;
172e21126ecSEd Tanous 
173e21126ecSEd Tanous     std::vector<std::filesystem::path> hostLoggerFiles;
174e21126ecSEd Tanous     if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
175e21126ecSEd Tanous     {
176e21126ecSEd Tanous         BMCWEB_LOG_DEBUG("Failed to get host log file path");
177e21126ecSEd Tanous         return;
178e21126ecSEd Tanous     }
179e21126ecSEd Tanous     // If we weren't provided top and skip limits, use the defaults.
180e21126ecSEd Tanous     size_t skip = delegatedQuery.skip.value_or(0);
1817945eeedSEd Tanous     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
182e21126ecSEd Tanous     size_t logCount = 0;
183e21126ecSEd Tanous     // This vector only store the entries we want to expose that
184e21126ecSEd Tanous     // control by skip and top.
185e21126ecSEd Tanous     std::vector<std::string> logEntries;
1867945eeedSEd Tanous     if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries, logCount))
187e21126ecSEd Tanous     {
188e21126ecSEd Tanous         messages::internalError(asyncResp->res);
189e21126ecSEd Tanous         return;
190e21126ecSEd Tanous     }
191e21126ecSEd Tanous     // If vector is empty, that means skip value larger than total
192e21126ecSEd Tanous     // log count
193e21126ecSEd Tanous     if (logEntries.empty())
194e21126ecSEd Tanous     {
195e21126ecSEd Tanous         asyncResp->res.jsonValue["Members@odata.count"] = logCount;
196e21126ecSEd Tanous         return;
197e21126ecSEd Tanous     }
198e21126ecSEd Tanous     if (!logEntries.empty())
199e21126ecSEd Tanous     {
200e21126ecSEd Tanous         for (size_t i = 0; i < logEntries.size(); i++)
201e21126ecSEd Tanous         {
202e21126ecSEd Tanous             nlohmann::json::object_t hostLogEntry;
2037945eeedSEd Tanous             fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i],
2047945eeedSEd Tanous                                     hostLogEntry);
205e21126ecSEd Tanous             logEntryArray.emplace_back(std::move(hostLogEntry));
206e21126ecSEd Tanous         }
207e21126ecSEd Tanous 
208e21126ecSEd Tanous         asyncResp->res.jsonValue["Members@odata.count"] = logCount;
209e21126ecSEd Tanous         if (skip + top < logCount)
210e21126ecSEd Tanous         {
211e21126ecSEd Tanous             asyncResp->res.jsonValue["Members@odata.nextLink"] =
212e21126ecSEd Tanous                 std::format(
213e21126ecSEd Tanous                     "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries?$skip=",
214e21126ecSEd Tanous                     BMCWEB_REDFISH_SYSTEM_URI_NAME) +
215e21126ecSEd Tanous                 std::to_string(skip + top);
216e21126ecSEd Tanous         }
217e21126ecSEd Tanous     }
218e21126ecSEd Tanous }
219e21126ecSEd Tanous 
2207945eeedSEd Tanous inline void handleSystemsLogServicesHostloggerEntriesEntryGet(
2217945eeedSEd Tanous     App& app, const crow::Request& req,
222e21126ecSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2237945eeedSEd Tanous     const std::string& systemName, const std::string& param)
2247945eeedSEd Tanous {
225e21126ecSEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
226e21126ecSEd Tanous     {
227e21126ecSEd Tanous         return;
228e21126ecSEd Tanous     }
229e21126ecSEd Tanous     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
230e21126ecSEd Tanous     {
231e21126ecSEd Tanous         // Option currently returns no systems.  TBD
232e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
233e21126ecSEd Tanous                                    systemName);
234e21126ecSEd Tanous         return;
235e21126ecSEd Tanous     }
236e21126ecSEd Tanous     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
237e21126ecSEd Tanous     {
238e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
239e21126ecSEd Tanous                                    systemName);
240e21126ecSEd Tanous         return;
241e21126ecSEd Tanous     }
242e21126ecSEd Tanous     std::string_view targetID = param;
243e21126ecSEd Tanous 
244e21126ecSEd Tanous     uint64_t idInt = 0;
245e21126ecSEd Tanous 
2467945eeedSEd Tanous     auto [ptr, ec] = std::from_chars(targetID.begin(), targetID.end(), idInt);
247e21126ecSEd Tanous     if (ec != std::errc{} || ptr != targetID.end())
248e21126ecSEd Tanous     {
2497945eeedSEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", param);
250e21126ecSEd Tanous         return;
251e21126ecSEd Tanous     }
252e21126ecSEd Tanous 
253e21126ecSEd Tanous     std::vector<std::filesystem::path> hostLoggerFiles;
254e21126ecSEd Tanous     if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
255e21126ecSEd Tanous     {
256e21126ecSEd Tanous         BMCWEB_LOG_DEBUG("Failed to get host log file path");
257e21126ecSEd Tanous         return;
258e21126ecSEd Tanous     }
259e21126ecSEd Tanous 
260e21126ecSEd Tanous     size_t logCount = 0;
261e21126ecSEd Tanous     size_t top = 1;
262e21126ecSEd Tanous     std::vector<std::string> logEntries;
263e21126ecSEd Tanous     // We can get specific entry by skip and top. For example, if we
264e21126ecSEd Tanous     // want to get nth entry, we can set skip = n-1 and top = 1 to
265e21126ecSEd Tanous     // get that entry
2667945eeedSEd Tanous     if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries,
2677945eeedSEd Tanous                               logCount))
268e21126ecSEd Tanous     {
269e21126ecSEd Tanous         messages::internalError(asyncResp->res);
270e21126ecSEd Tanous         return;
271e21126ecSEd Tanous     }
272e21126ecSEd Tanous 
273e21126ecSEd Tanous     if (!logEntries.empty())
274e21126ecSEd Tanous     {
275e21126ecSEd Tanous         nlohmann::json::object_t hostLogEntry;
2767945eeedSEd Tanous         fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry);
277e21126ecSEd Tanous         asyncResp->res.jsonValue.update(hostLogEntry);
278e21126ecSEd Tanous         return;
279e21126ecSEd Tanous     }
280e21126ecSEd Tanous 
281e21126ecSEd Tanous     // Requested ID was not found
282e21126ecSEd Tanous     messages::resourceNotFound(asyncResp->res, "LogEntry", param);
283e21126ecSEd Tanous }
284e21126ecSEd Tanous 
285e21126ecSEd Tanous inline void requestRoutesSystemsLogServiceHostlogger(App& app)
286e21126ecSEd Tanous {
2877945eeedSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/")
2887945eeedSEd Tanous         .privileges(redfish::privileges::getLogService)
2897945eeedSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
2907945eeedSEd Tanous             handleSystemsLogServicesHostloggerGet, std::ref(app)));
2917945eeedSEd Tanous     BMCWEB_ROUTE(app,
2927945eeedSEd Tanous                  "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/")
2933d45ef66SGunnar Mills         .privileges(redfish::privileges::getLogEntryCollection)
2947945eeedSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
2957945eeedSEd Tanous             handleSystemsLogServicesHostloggerEntriesGet, std::ref(app)));
2967945eeedSEd Tanous 
2977945eeedSEd Tanous     BMCWEB_ROUTE(
2987945eeedSEd Tanous         app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/")
2997945eeedSEd Tanous         .privileges(redfish::privileges::getLogEntry)
3007945eeedSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
3017945eeedSEd Tanous             handleSystemsLogServicesHostloggerEntriesEntryGet, std::ref(app)));
302e21126ecSEd Tanous }
303e21126ecSEd Tanous 
304e21126ecSEd Tanous } // namespace redfish
305