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