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