xref: /openbmc/bmcweb/redfish-core/lib/systems_logservices_hostlogger.hpp (revision 504af5a0568171b72caf13234cc81380b261fa21)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3e21126ecSEd Tanous #pragma once
4e21126ecSEd Tanous 
5d7857201SEd Tanous #include "bmcweb_config.h"
6d7857201SEd Tanous 
7e21126ecSEd Tanous #include "app.hpp"
8d7857201SEd Tanous #include "async_resp.hpp"
9d7857201SEd Tanous #include "error_messages.hpp"
10e21126ecSEd Tanous #include "generated/enums/log_entry.hpp"
113d66430aSEd Tanous #include "gzfile.hpp"
12d7857201SEd Tanous #include "http_request.hpp"
13d7857201SEd Tanous #include "human_sort.hpp"
14d7857201SEd Tanous #include "logging.hpp"
15e21126ecSEd Tanous #include "query.hpp"
16e21126ecSEd Tanous #include "registries/privilege_registry.hpp"
17d7857201SEd Tanous #include "utils/query_param.hpp"
18e21126ecSEd Tanous 
19d7857201SEd Tanous #include <boost/beast/http/verb.hpp>
20d7857201SEd Tanous #include <boost/url/format.hpp>
21d7857201SEd Tanous #include <nlohmann/json.hpp>
22d7857201SEd Tanous 
23d7857201SEd Tanous #include <algorithm>
24d7857201SEd Tanous #include <charconv>
25d7857201SEd Tanous #include <cstddef>
26e21126ecSEd Tanous #include <cstdint>
27d7857201SEd Tanous #include <filesystem>
28d7857201SEd Tanous #include <format>
29d7857201SEd Tanous #include <functional>
30e21126ecSEd Tanous #include <memory>
31d7857201SEd Tanous #include <string>
32e21126ecSEd Tanous #include <string_view>
33d7857201SEd Tanous #include <system_error>
34e21126ecSEd Tanous #include <utility>
35e21126ecSEd Tanous #include <vector>
36e21126ecSEd Tanous 
37e21126ecSEd Tanous namespace redfish
38e21126ecSEd Tanous {
393d66430aSEd Tanous constexpr const char* hostLoggerFolderPath = "/var/log/console";
403d66430aSEd Tanous 
getHostLoggerFiles(const std::string & hostLoggerFilePath,std::vector<std::filesystem::path> & hostLoggerFiles)41*504af5a0SPatrick Williams inline bool getHostLoggerFiles(
42*504af5a0SPatrick Williams     const std::string& hostLoggerFilePath,
433d66430aSEd Tanous     std::vector<std::filesystem::path>& hostLoggerFiles)
443d66430aSEd Tanous {
453d66430aSEd Tanous     std::error_code ec;
463d66430aSEd Tanous     std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec);
473d66430aSEd Tanous     if (ec)
483d66430aSEd Tanous     {
493d66430aSEd Tanous         BMCWEB_LOG_WARNING("{}", ec.message());
503d66430aSEd Tanous         return false;
513d66430aSEd Tanous     }
523d66430aSEd Tanous     for (const std::filesystem::directory_entry& it : logPath)
533d66430aSEd Tanous     {
543d66430aSEd Tanous         std::string filename = it.path().filename();
553d66430aSEd Tanous         // Prefix of each log files is "log". Find the file and save the
563d66430aSEd Tanous         // path
573d66430aSEd Tanous         if (filename.starts_with("log"))
583d66430aSEd Tanous         {
593d66430aSEd Tanous             hostLoggerFiles.emplace_back(it.path());
603d66430aSEd Tanous         }
613d66430aSEd Tanous     }
623d66430aSEd Tanous     // As the log files rotate, they are appended with a ".#" that is higher for
633d66430aSEd Tanous     // the older logs. Since we start from oldest logs, sort the name in
643d66430aSEd Tanous     // descending order.
653d66430aSEd Tanous     std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(),
663d66430aSEd Tanous               AlphanumLess<std::string>());
673d66430aSEd Tanous 
683d66430aSEd Tanous     return true;
693d66430aSEd Tanous }
703d66430aSEd Tanous 
getHostLoggerEntries(const std::vector<std::filesystem::path> & hostLoggerFiles,uint64_t skip,uint64_t top,std::vector<std::string> & logEntries,size_t & logCount)713d66430aSEd Tanous inline bool getHostLoggerEntries(
723d66430aSEd Tanous     const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip,
733d66430aSEd Tanous     uint64_t top, std::vector<std::string>& logEntries, size_t& logCount)
743d66430aSEd Tanous {
753d66430aSEd Tanous     GzFileReader logFile;
763d66430aSEd Tanous 
773d66430aSEd Tanous     // Go though all log files and expose host logs.
783d66430aSEd Tanous     for (const std::filesystem::path& it : hostLoggerFiles)
793d66430aSEd Tanous     {
803d66430aSEd Tanous         if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount))
813d66430aSEd Tanous         {
823d66430aSEd Tanous             BMCWEB_LOG_ERROR("fail to expose host logs");
833d66430aSEd Tanous             return false;
843d66430aSEd Tanous         }
853d66430aSEd Tanous     }
863d66430aSEd Tanous     // Get lastMessage from constructor by getter
873d66430aSEd Tanous     std::string lastMessage = logFile.getLastMessage();
883d66430aSEd Tanous     if (!lastMessage.empty())
893d66430aSEd Tanous     {
903d66430aSEd Tanous         logCount++;
913d66430aSEd Tanous         if (logCount > skip && logCount <= (skip + top))
923d66430aSEd Tanous         {
933d66430aSEd Tanous             logEntries.push_back(lastMessage);
943d66430aSEd Tanous         }
953d66430aSEd Tanous     }
963d66430aSEd Tanous     return true;
973d66430aSEd Tanous }
98e21126ecSEd Tanous 
fillHostLoggerEntryJson(std::string_view logEntryID,std::string_view msg,nlohmann::json::object_t & logEntryJson)99e21126ecSEd Tanous inline void fillHostLoggerEntryJson(std::string_view logEntryID,
100e21126ecSEd Tanous                                     std::string_view msg,
101e21126ecSEd Tanous                                     nlohmann::json::object_t& logEntryJson)
102e21126ecSEd Tanous {
103e21126ecSEd Tanous     // Fill in the log entry with the gathered data.
104e21126ecSEd Tanous     logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
105e21126ecSEd Tanous     logEntryJson["@odata.id"] = boost::urls::format(
106e21126ecSEd Tanous         "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries/{}",
107e21126ecSEd Tanous         BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
108e21126ecSEd Tanous     logEntryJson["Name"] = "Host Logger Entry";
109e21126ecSEd Tanous     logEntryJson["Id"] = logEntryID;
110e21126ecSEd Tanous     logEntryJson["Message"] = msg;
111e21126ecSEd Tanous     logEntryJson["EntryType"] = log_entry::LogEntryType::Oem;
112e21126ecSEd Tanous     logEntryJson["Severity"] = log_entry::EventSeverity::OK;
113e21126ecSEd Tanous     logEntryJson["OemRecordFormat"] = "Host Logger Entry";
114e21126ecSEd Tanous }
115e21126ecSEd Tanous 
handleSystemsLogServicesHostloggerGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)1167945eeedSEd Tanous inline void handleSystemsLogServicesHostloggerGet(
1177945eeedSEd Tanous     App& app, const crow::Request& req,
118e21126ecSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1197945eeedSEd Tanous     const std::string& systemName)
1207945eeedSEd Tanous {
121e21126ecSEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
122e21126ecSEd Tanous     {
123e21126ecSEd Tanous         return;
124e21126ecSEd Tanous     }
125e21126ecSEd Tanous     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
126e21126ecSEd Tanous     {
127e21126ecSEd Tanous         // Option currently returns no systems.  TBD
128e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
129e21126ecSEd Tanous                                    systemName);
130e21126ecSEd Tanous         return;
131e21126ecSEd Tanous     }
132e21126ecSEd Tanous     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
133e21126ecSEd Tanous     {
134e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
135e21126ecSEd Tanous                                    systemName);
136e21126ecSEd Tanous         return;
137e21126ecSEd Tanous     }
138e21126ecSEd Tanous     asyncResp->res.jsonValue["@odata.id"] =
139e21126ecSEd Tanous         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger",
140e21126ecSEd Tanous                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
1417945eeedSEd Tanous     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
142e21126ecSEd Tanous     asyncResp->res.jsonValue["Name"] = "Host Logger Service";
143e21126ecSEd Tanous     asyncResp->res.jsonValue["Description"] = "Host Logger Service";
144e21126ecSEd Tanous     asyncResp->res.jsonValue["Id"] = "HostLogger";
1457945eeedSEd Tanous     asyncResp->res.jsonValue["Entries"]["@odata.id"] =
1467945eeedSEd Tanous         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
147e21126ecSEd Tanous                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
148e21126ecSEd Tanous }
149e21126ecSEd Tanous 
handleSystemsLogServicesHostloggerEntriesGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)1507945eeedSEd Tanous inline void handleSystemsLogServicesHostloggerEntriesGet(
1517945eeedSEd Tanous     App& app, const crow::Request& req,
152e21126ecSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1537945eeedSEd Tanous     const std::string& systemName)
1547945eeedSEd Tanous {
155e21126ecSEd Tanous     query_param::QueryCapabilities capabilities = {
156e21126ecSEd Tanous         .canDelegateTop = true,
157e21126ecSEd Tanous         .canDelegateSkip = true,
158e21126ecSEd Tanous     };
159e21126ecSEd Tanous     query_param::Query delegatedQuery;
1607945eeedSEd Tanous     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
1617945eeedSEd Tanous                                                   delegatedQuery, capabilities))
162e21126ecSEd Tanous     {
163e21126ecSEd Tanous         return;
164e21126ecSEd Tanous     }
165e21126ecSEd Tanous     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
166e21126ecSEd Tanous     {
167e21126ecSEd Tanous         // Option currently returns no systems.  TBD
168e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
169e21126ecSEd Tanous                                    systemName);
170e21126ecSEd Tanous         return;
171e21126ecSEd Tanous     }
172e21126ecSEd Tanous     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
173e21126ecSEd Tanous     {
174e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
175e21126ecSEd Tanous                                    systemName);
176e21126ecSEd Tanous         return;
177e21126ecSEd Tanous     }
1787945eeedSEd Tanous     asyncResp->res.jsonValue["@odata.id"] =
1797945eeedSEd Tanous         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
180e21126ecSEd Tanous                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
181e21126ecSEd Tanous     asyncResp->res.jsonValue["@odata.type"] =
182e21126ecSEd Tanous         "#LogEntryCollection.LogEntryCollection";
183e21126ecSEd Tanous     asyncResp->res.jsonValue["Name"] = "HostLogger Entries";
184e21126ecSEd Tanous     asyncResp->res.jsonValue["Description"] =
185e21126ecSEd Tanous         "Collection of HostLogger Entries";
186e21126ecSEd Tanous     nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
187e21126ecSEd Tanous     logEntryArray = nlohmann::json::array();
188e21126ecSEd Tanous     asyncResp->res.jsonValue["Members@odata.count"] = 0;
189e21126ecSEd Tanous 
190e21126ecSEd Tanous     std::vector<std::filesystem::path> hostLoggerFiles;
191e21126ecSEd Tanous     if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
192e21126ecSEd Tanous     {
193e21126ecSEd Tanous         BMCWEB_LOG_DEBUG("Failed to get host log file path");
194e21126ecSEd Tanous         return;
195e21126ecSEd Tanous     }
196e21126ecSEd Tanous     // If we weren't provided top and skip limits, use the defaults.
197e21126ecSEd Tanous     size_t skip = delegatedQuery.skip.value_or(0);
1987945eeedSEd Tanous     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
199e21126ecSEd Tanous     size_t logCount = 0;
200e21126ecSEd Tanous     // This vector only store the entries we want to expose that
201e21126ecSEd Tanous     // control by skip and top.
202e21126ecSEd Tanous     std::vector<std::string> logEntries;
2037945eeedSEd Tanous     if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries, logCount))
204e21126ecSEd Tanous     {
205e21126ecSEd Tanous         messages::internalError(asyncResp->res);
206e21126ecSEd Tanous         return;
207e21126ecSEd Tanous     }
208e21126ecSEd Tanous     // If vector is empty, that means skip value larger than total
209e21126ecSEd Tanous     // log count
210e21126ecSEd Tanous     if (logEntries.empty())
211e21126ecSEd Tanous     {
212e21126ecSEd Tanous         asyncResp->res.jsonValue["Members@odata.count"] = logCount;
213e21126ecSEd Tanous         return;
214e21126ecSEd Tanous     }
215e21126ecSEd Tanous     if (!logEntries.empty())
216e21126ecSEd Tanous     {
217e21126ecSEd Tanous         for (size_t i = 0; i < logEntries.size(); i++)
218e21126ecSEd Tanous         {
219e21126ecSEd Tanous             nlohmann::json::object_t hostLogEntry;
2207945eeedSEd Tanous             fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i],
2217945eeedSEd Tanous                                     hostLogEntry);
222e21126ecSEd Tanous             logEntryArray.emplace_back(std::move(hostLogEntry));
223e21126ecSEd Tanous         }
224e21126ecSEd Tanous 
225e21126ecSEd Tanous         asyncResp->res.jsonValue["Members@odata.count"] = logCount;
226e21126ecSEd Tanous         if (skip + top < logCount)
227e21126ecSEd Tanous         {
228e21126ecSEd Tanous             asyncResp->res.jsonValue["Members@odata.nextLink"] =
229e21126ecSEd Tanous                 std::format(
230e21126ecSEd Tanous                     "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries?$skip=",
231e21126ecSEd Tanous                     BMCWEB_REDFISH_SYSTEM_URI_NAME) +
232e21126ecSEd Tanous                 std::to_string(skip + top);
233e21126ecSEd Tanous         }
234e21126ecSEd Tanous     }
235e21126ecSEd Tanous }
236e21126ecSEd Tanous 
handleSystemsLogServicesHostloggerEntriesEntryGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & param)2377945eeedSEd Tanous inline void handleSystemsLogServicesHostloggerEntriesEntryGet(
2387945eeedSEd Tanous     App& app, const crow::Request& req,
239e21126ecSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2407945eeedSEd Tanous     const std::string& systemName, const std::string& param)
2417945eeedSEd Tanous {
242e21126ecSEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
243e21126ecSEd Tanous     {
244e21126ecSEd Tanous         return;
245e21126ecSEd Tanous     }
246e21126ecSEd Tanous     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
247e21126ecSEd Tanous     {
248e21126ecSEd Tanous         // Option currently returns no systems.  TBD
249e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
250e21126ecSEd Tanous                                    systemName);
251e21126ecSEd Tanous         return;
252e21126ecSEd Tanous     }
253e21126ecSEd Tanous     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
254e21126ecSEd Tanous     {
255e21126ecSEd Tanous         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
256e21126ecSEd Tanous                                    systemName);
257e21126ecSEd Tanous         return;
258e21126ecSEd Tanous     }
259e21126ecSEd Tanous     std::string_view targetID = param;
260e21126ecSEd Tanous 
261e21126ecSEd Tanous     uint64_t idInt = 0;
262e21126ecSEd Tanous 
2637945eeedSEd Tanous     auto [ptr, ec] = std::from_chars(targetID.begin(), targetID.end(), idInt);
264e21126ecSEd Tanous     if (ec != std::errc{} || ptr != targetID.end())
265e21126ecSEd Tanous     {
2667945eeedSEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", param);
267e21126ecSEd Tanous         return;
268e21126ecSEd Tanous     }
269e21126ecSEd Tanous 
270e21126ecSEd Tanous     std::vector<std::filesystem::path> hostLoggerFiles;
271e21126ecSEd Tanous     if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
272e21126ecSEd Tanous     {
273e21126ecSEd Tanous         BMCWEB_LOG_DEBUG("Failed to get host log file path");
274e21126ecSEd Tanous         return;
275e21126ecSEd Tanous     }
276e21126ecSEd Tanous 
277e21126ecSEd Tanous     size_t logCount = 0;
278e21126ecSEd Tanous     size_t top = 1;
279e21126ecSEd Tanous     std::vector<std::string> logEntries;
280e21126ecSEd Tanous     // We can get specific entry by skip and top. For example, if we
281e21126ecSEd Tanous     // want to get nth entry, we can set skip = n-1 and top = 1 to
282e21126ecSEd Tanous     // get that entry
2837945eeedSEd Tanous     if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries,
2847945eeedSEd Tanous                               logCount))
285e21126ecSEd Tanous     {
286e21126ecSEd Tanous         messages::internalError(asyncResp->res);
287e21126ecSEd Tanous         return;
288e21126ecSEd Tanous     }
289e21126ecSEd Tanous 
290e21126ecSEd Tanous     if (!logEntries.empty())
291e21126ecSEd Tanous     {
292e21126ecSEd Tanous         nlohmann::json::object_t hostLogEntry;
2937945eeedSEd Tanous         fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry);
294e21126ecSEd Tanous         asyncResp->res.jsonValue.update(hostLogEntry);
295e21126ecSEd Tanous         return;
296e21126ecSEd Tanous     }
297e21126ecSEd Tanous 
298e21126ecSEd Tanous     // Requested ID was not found
299e21126ecSEd Tanous     messages::resourceNotFound(asyncResp->res, "LogEntry", param);
300e21126ecSEd Tanous }
301e21126ecSEd Tanous 
requestRoutesSystemsLogServiceHostlogger(App & app)302e21126ecSEd Tanous inline void requestRoutesSystemsLogServiceHostlogger(App& app)
303e21126ecSEd Tanous {
3047945eeedSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/")
3057945eeedSEd Tanous         .privileges(redfish::privileges::getLogService)
3067945eeedSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
3077945eeedSEd Tanous             handleSystemsLogServicesHostloggerGet, std::ref(app)));
3087945eeedSEd Tanous     BMCWEB_ROUTE(app,
3097945eeedSEd Tanous                  "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/")
3103d45ef66SGunnar Mills         .privileges(redfish::privileges::getLogEntryCollection)
3117945eeedSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
3127945eeedSEd Tanous             handleSystemsLogServicesHostloggerEntriesGet, std::ref(app)));
3137945eeedSEd Tanous 
3147945eeedSEd Tanous     BMCWEB_ROUTE(
3157945eeedSEd Tanous         app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/")
3167945eeedSEd Tanous         .privileges(redfish::privileges::getLogEntry)
3177945eeedSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
3187945eeedSEd Tanous             handleSystemsLogServicesHostloggerEntriesEntryGet, std::ref(app)));
319e21126ecSEd Tanous }
320e21126ecSEd Tanous 
321e21126ecSEd Tanous } // namespace redfish
322