1*7681b8a1SOliver Brewka // SPDX-License-Identifier: Apache-2.0 2*7681b8a1SOliver Brewka // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3*7681b8a1SOliver Brewka // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 4*7681b8a1SOliver Brewka #pragma once 5*7681b8a1SOliver Brewka 6*7681b8a1SOliver Brewka #include "async_resp.hpp" 7*7681b8a1SOliver Brewka #include "dbus_utility.hpp" 8*7681b8a1SOliver Brewka #include "error_messages.hpp" 9*7681b8a1SOliver Brewka #include "http_response.hpp" 10*7681b8a1SOliver Brewka #include "logging.hpp" 11*7681b8a1SOliver Brewka #include "registries.hpp" 12*7681b8a1SOliver Brewka #include "str_utility.hpp" 13*7681b8a1SOliver Brewka #include "utils/query_param.hpp" 14*7681b8a1SOliver Brewka 15*7681b8a1SOliver Brewka #include <boost/beast/http/field.hpp> 16*7681b8a1SOliver Brewka #include <boost/beast/http/status.hpp> 17*7681b8a1SOliver Brewka #include <boost/beast/http/verb.hpp> 18*7681b8a1SOliver Brewka #include <boost/system/linux_error.hpp> 19*7681b8a1SOliver Brewka #include <boost/url/format.hpp> 20*7681b8a1SOliver Brewka #include <boost/url/url.hpp> 21*7681b8a1SOliver Brewka #include <sdbusplus/message.hpp> 22*7681b8a1SOliver Brewka #include <sdbusplus/message/native_types.hpp> 23*7681b8a1SOliver Brewka #include <sdbusplus/unpack_properties.hpp> 24*7681b8a1SOliver Brewka 25*7681b8a1SOliver Brewka #include <algorithm> 26*7681b8a1SOliver Brewka #include <cstddef> 27*7681b8a1SOliver Brewka #include <cstdint> 28*7681b8a1SOliver Brewka #include <cstdio> 29*7681b8a1SOliver Brewka #include <ctime> 30*7681b8a1SOliver Brewka #include <fstream> 31*7681b8a1SOliver Brewka #include <iomanip> 32*7681b8a1SOliver Brewka #include <sstream> 33*7681b8a1SOliver Brewka #include <string> 34*7681b8a1SOliver Brewka #include <utility> 35*7681b8a1SOliver Brewka 36*7681b8a1SOliver Brewka namespace redfish 37*7681b8a1SOliver Brewka { 38*7681b8a1SOliver Brewka namespace eventlog_utils 39*7681b8a1SOliver Brewka { 40*7681b8a1SOliver Brewka 41*7681b8a1SOliver Brewka /* 42*7681b8a1SOliver Brewka * Journal EventLog utilities 43*7681b8a1SOliver Brewka * */ 44*7681b8a1SOliver Brewka 45*7681b8a1SOliver Brewka inline bool getRedfishLogFiles( 46*7681b8a1SOliver Brewka std::vector<std::filesystem::path>& redfishLogFiles) 47*7681b8a1SOliver Brewka { 48*7681b8a1SOliver Brewka static const std::filesystem::path redfishLogDir = "/var/log"; 49*7681b8a1SOliver Brewka static const std::string redfishLogFilename = "redfish"; 50*7681b8a1SOliver Brewka 51*7681b8a1SOliver Brewka // Loop through the directory looking for redfish log files 52*7681b8a1SOliver Brewka for (const std::filesystem::directory_entry& dirEnt : 53*7681b8a1SOliver Brewka std::filesystem::directory_iterator(redfishLogDir)) 54*7681b8a1SOliver Brewka { 55*7681b8a1SOliver Brewka // If we find a redfish log file, save the path 56*7681b8a1SOliver Brewka std::string filename = dirEnt.path().filename(); 57*7681b8a1SOliver Brewka if (filename.starts_with(redfishLogFilename)) 58*7681b8a1SOliver Brewka { 59*7681b8a1SOliver Brewka redfishLogFiles.emplace_back(redfishLogDir / filename); 60*7681b8a1SOliver Brewka } 61*7681b8a1SOliver Brewka } 62*7681b8a1SOliver Brewka // As the log files rotate, they are appended with a ".#" that is higher for 63*7681b8a1SOliver Brewka // the older logs. Since we don't expect more than 10 log files, we 64*7681b8a1SOliver Brewka // can just sort the list to get them in order from newest to oldest 65*7681b8a1SOliver Brewka std::ranges::sort(redfishLogFiles); 66*7681b8a1SOliver Brewka 67*7681b8a1SOliver Brewka return !redfishLogFiles.empty(); 68*7681b8a1SOliver Brewka } 69*7681b8a1SOliver Brewka 70*7681b8a1SOliver Brewka inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID, 71*7681b8a1SOliver Brewka const bool firstEntry = true) 72*7681b8a1SOliver Brewka { 73*7681b8a1SOliver Brewka static time_t prevTs = 0; 74*7681b8a1SOliver Brewka static int index = 0; 75*7681b8a1SOliver Brewka if (firstEntry) 76*7681b8a1SOliver Brewka { 77*7681b8a1SOliver Brewka prevTs = 0; 78*7681b8a1SOliver Brewka } 79*7681b8a1SOliver Brewka 80*7681b8a1SOliver Brewka // Get the entry timestamp 81*7681b8a1SOliver Brewka std::time_t curTs = 0; 82*7681b8a1SOliver Brewka std::tm timeStruct = {}; 83*7681b8a1SOliver Brewka std::istringstream entryStream(logEntry); 84*7681b8a1SOliver Brewka if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 85*7681b8a1SOliver Brewka { 86*7681b8a1SOliver Brewka curTs = std::mktime(&timeStruct); 87*7681b8a1SOliver Brewka } 88*7681b8a1SOliver Brewka // If the timestamp isn't unique, increment the index 89*7681b8a1SOliver Brewka if (curTs == prevTs) 90*7681b8a1SOliver Brewka { 91*7681b8a1SOliver Brewka index++; 92*7681b8a1SOliver Brewka } 93*7681b8a1SOliver Brewka else 94*7681b8a1SOliver Brewka { 95*7681b8a1SOliver Brewka // Otherwise, reset it 96*7681b8a1SOliver Brewka index = 0; 97*7681b8a1SOliver Brewka } 98*7681b8a1SOliver Brewka // Save the timestamp 99*7681b8a1SOliver Brewka prevTs = curTs; 100*7681b8a1SOliver Brewka 101*7681b8a1SOliver Brewka entryID = std::to_string(curTs); 102*7681b8a1SOliver Brewka if (index > 0) 103*7681b8a1SOliver Brewka { 104*7681b8a1SOliver Brewka entryID += "_" + std::to_string(index); 105*7681b8a1SOliver Brewka } 106*7681b8a1SOliver Brewka return true; 107*7681b8a1SOliver Brewka } 108*7681b8a1SOliver Brewka 109*7681b8a1SOliver Brewka enum class LogParseError 110*7681b8a1SOliver Brewka { 111*7681b8a1SOliver Brewka success, 112*7681b8a1SOliver Brewka parseFailed, 113*7681b8a1SOliver Brewka messageIdNotInRegistry, 114*7681b8a1SOliver Brewka }; 115*7681b8a1SOliver Brewka 116*7681b8a1SOliver Brewka static LogParseError fillEventLogEntryJson( 117*7681b8a1SOliver Brewka const std::string& logEntryID, const std::string& logEntry, 118*7681b8a1SOliver Brewka nlohmann::json::object_t& logEntryJson) 119*7681b8a1SOliver Brewka { 120*7681b8a1SOliver Brewka // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 121*7681b8a1SOliver Brewka // First get the Timestamp 122*7681b8a1SOliver Brewka size_t space = logEntry.find_first_of(' '); 123*7681b8a1SOliver Brewka if (space == std::string::npos) 124*7681b8a1SOliver Brewka { 125*7681b8a1SOliver Brewka return LogParseError::parseFailed; 126*7681b8a1SOliver Brewka } 127*7681b8a1SOliver Brewka std::string timestamp = logEntry.substr(0, space); 128*7681b8a1SOliver Brewka // Then get the log contents 129*7681b8a1SOliver Brewka size_t entryStart = logEntry.find_first_not_of(' ', space); 130*7681b8a1SOliver Brewka if (entryStart == std::string::npos) 131*7681b8a1SOliver Brewka { 132*7681b8a1SOliver Brewka return LogParseError::parseFailed; 133*7681b8a1SOliver Brewka } 134*7681b8a1SOliver Brewka std::string_view entry(logEntry); 135*7681b8a1SOliver Brewka entry.remove_prefix(entryStart); 136*7681b8a1SOliver Brewka // Use split to separate the entry into its fields 137*7681b8a1SOliver Brewka std::vector<std::string> logEntryFields; 138*7681b8a1SOliver Brewka bmcweb::split(logEntryFields, entry, ','); 139*7681b8a1SOliver Brewka // We need at least a MessageId to be valid 140*7681b8a1SOliver Brewka auto logEntryIter = logEntryFields.begin(); 141*7681b8a1SOliver Brewka if (logEntryIter == logEntryFields.end()) 142*7681b8a1SOliver Brewka { 143*7681b8a1SOliver Brewka return LogParseError::parseFailed; 144*7681b8a1SOliver Brewka } 145*7681b8a1SOliver Brewka std::string& messageID = *logEntryIter; 146*7681b8a1SOliver Brewka // Get the Message from the MessageRegistry 147*7681b8a1SOliver Brewka const registries::Message* message = registries::getMessage(messageID); 148*7681b8a1SOliver Brewka 149*7681b8a1SOliver Brewka logEntryIter++; 150*7681b8a1SOliver Brewka if (message == nullptr) 151*7681b8a1SOliver Brewka { 152*7681b8a1SOliver Brewka BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry); 153*7681b8a1SOliver Brewka return LogParseError::messageIdNotInRegistry; 154*7681b8a1SOliver Brewka } 155*7681b8a1SOliver Brewka 156*7681b8a1SOliver Brewka std::vector<std::string_view> messageArgs(logEntryIter, 157*7681b8a1SOliver Brewka logEntryFields.end()); 158*7681b8a1SOliver Brewka messageArgs.resize(message->numberOfArgs); 159*7681b8a1SOliver Brewka 160*7681b8a1SOliver Brewka std::string msg = 161*7681b8a1SOliver Brewka redfish::registries::fillMessageArgs(messageArgs, message->message); 162*7681b8a1SOliver Brewka if (msg.empty()) 163*7681b8a1SOliver Brewka { 164*7681b8a1SOliver Brewka return LogParseError::parseFailed; 165*7681b8a1SOliver Brewka } 166*7681b8a1SOliver Brewka 167*7681b8a1SOliver Brewka // Get the Created time from the timestamp. The log timestamp is in RFC3339 168*7681b8a1SOliver Brewka // format which matches the Redfish format except for the fractional seconds 169*7681b8a1SOliver Brewka // between the '.' and the '+', so just remove them. 170*7681b8a1SOliver Brewka std::size_t dot = timestamp.find_first_of('.'); 171*7681b8a1SOliver Brewka std::size_t plus = timestamp.find_first_of('+'); 172*7681b8a1SOliver Brewka if (dot != std::string::npos && plus != std::string::npos) 173*7681b8a1SOliver Brewka { 174*7681b8a1SOliver Brewka timestamp.erase(dot, plus - dot); 175*7681b8a1SOliver Brewka } 176*7681b8a1SOliver Brewka 177*7681b8a1SOliver Brewka // Fill in the log entry with the gathered data 178*7681b8a1SOliver Brewka logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 179*7681b8a1SOliver Brewka logEntryJson["@odata.id"] = boost::urls::format( 180*7681b8a1SOliver Brewka "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}", 181*7681b8a1SOliver Brewka BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID); 182*7681b8a1SOliver Brewka logEntryJson["Name"] = "System Event Log Entry"; 183*7681b8a1SOliver Brewka logEntryJson["Id"] = logEntryID; 184*7681b8a1SOliver Brewka logEntryJson["Message"] = std::move(msg); 185*7681b8a1SOliver Brewka logEntryJson["MessageId"] = std::move(messageID); 186*7681b8a1SOliver Brewka logEntryJson["MessageArgs"] = messageArgs; 187*7681b8a1SOliver Brewka logEntryJson["EntryType"] = "Event"; 188*7681b8a1SOliver Brewka logEntryJson["Severity"] = message->messageSeverity; 189*7681b8a1SOliver Brewka logEntryJson["Created"] = std::move(timestamp); 190*7681b8a1SOliver Brewka return LogParseError::success; 191*7681b8a1SOliver Brewka } 192*7681b8a1SOliver Brewka 193*7681b8a1SOliver Brewka inline void handleRequestSystemsLogServiceEventLogLogEntryCollection( 194*7681b8a1SOliver Brewka const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 195*7681b8a1SOliver Brewka query_param::Query& delegatedQuery) 196*7681b8a1SOliver Brewka { 197*7681b8a1SOliver Brewka size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 198*7681b8a1SOliver Brewka size_t skip = delegatedQuery.skip.value_or(0); 199*7681b8a1SOliver Brewka 200*7681b8a1SOliver Brewka // Collections don't include the static data added by SubRoute 201*7681b8a1SOliver Brewka // because it has a duplicate entry for members 202*7681b8a1SOliver Brewka asyncResp->res.jsonValue["@odata.type"] = 203*7681b8a1SOliver Brewka "#LogEntryCollection.LogEntryCollection"; 204*7681b8a1SOliver Brewka asyncResp->res.jsonValue["@odata.id"] = 205*7681b8a1SOliver Brewka std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries", 206*7681b8a1SOliver Brewka BMCWEB_REDFISH_SYSTEM_URI_NAME); 207*7681b8a1SOliver Brewka asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 208*7681b8a1SOliver Brewka asyncResp->res.jsonValue["Description"] = 209*7681b8a1SOliver Brewka "Collection of System Event Log Entries"; 210*7681b8a1SOliver Brewka 211*7681b8a1SOliver Brewka nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 212*7681b8a1SOliver Brewka logEntryArray = nlohmann::json::array(); 213*7681b8a1SOliver Brewka // Go through the log files and create a unique ID for each 214*7681b8a1SOliver Brewka // entry 215*7681b8a1SOliver Brewka std::vector<std::filesystem::path> redfishLogFiles; 216*7681b8a1SOliver Brewka getRedfishLogFiles(redfishLogFiles); 217*7681b8a1SOliver Brewka uint64_t entryCount = 0; 218*7681b8a1SOliver Brewka std::string logEntry; 219*7681b8a1SOliver Brewka 220*7681b8a1SOliver Brewka // Oldest logs are in the last file, so start there and loop 221*7681b8a1SOliver Brewka // backwards 222*7681b8a1SOliver Brewka for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++) 223*7681b8a1SOliver Brewka { 224*7681b8a1SOliver Brewka std::ifstream logStream(*it); 225*7681b8a1SOliver Brewka if (!logStream.is_open()) 226*7681b8a1SOliver Brewka { 227*7681b8a1SOliver Brewka continue; 228*7681b8a1SOliver Brewka } 229*7681b8a1SOliver Brewka 230*7681b8a1SOliver Brewka // Reset the unique ID on the first entry 231*7681b8a1SOliver Brewka bool firstEntry = true; 232*7681b8a1SOliver Brewka while (std::getline(logStream, logEntry)) 233*7681b8a1SOliver Brewka { 234*7681b8a1SOliver Brewka std::string idStr; 235*7681b8a1SOliver Brewka if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 236*7681b8a1SOliver Brewka { 237*7681b8a1SOliver Brewka continue; 238*7681b8a1SOliver Brewka } 239*7681b8a1SOliver Brewka firstEntry = false; 240*7681b8a1SOliver Brewka 241*7681b8a1SOliver Brewka nlohmann::json::object_t bmcLogEntry; 242*7681b8a1SOliver Brewka LogParseError status = 243*7681b8a1SOliver Brewka fillEventLogEntryJson(idStr, logEntry, bmcLogEntry); 244*7681b8a1SOliver Brewka if (status == LogParseError::messageIdNotInRegistry) 245*7681b8a1SOliver Brewka { 246*7681b8a1SOliver Brewka continue; 247*7681b8a1SOliver Brewka } 248*7681b8a1SOliver Brewka if (status != LogParseError::success) 249*7681b8a1SOliver Brewka { 250*7681b8a1SOliver Brewka messages::internalError(asyncResp->res); 251*7681b8a1SOliver Brewka return; 252*7681b8a1SOliver Brewka } 253*7681b8a1SOliver Brewka 254*7681b8a1SOliver Brewka entryCount++; 255*7681b8a1SOliver Brewka // Handle paging using skip (number of entries to skip from the 256*7681b8a1SOliver Brewka // start) and top (number of entries to display) 257*7681b8a1SOliver Brewka if (entryCount <= skip || entryCount > skip + top) 258*7681b8a1SOliver Brewka { 259*7681b8a1SOliver Brewka continue; 260*7681b8a1SOliver Brewka } 261*7681b8a1SOliver Brewka 262*7681b8a1SOliver Brewka logEntryArray.emplace_back(std::move(bmcLogEntry)); 263*7681b8a1SOliver Brewka } 264*7681b8a1SOliver Brewka } 265*7681b8a1SOliver Brewka asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 266*7681b8a1SOliver Brewka if (skip + top < entryCount) 267*7681b8a1SOliver Brewka { 268*7681b8a1SOliver Brewka asyncResp->res.jsonValue["Members@odata.nextLink"] = 269*7681b8a1SOliver Brewka boost::urls::format( 270*7681b8a1SOliver Brewka "/redfish/v1/Systems/{}/LogServices/EventLog/Entries?$skip={}", 271*7681b8a1SOliver Brewka BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(skip + top)); 272*7681b8a1SOliver Brewka } 273*7681b8a1SOliver Brewka } 274*7681b8a1SOliver Brewka 275*7681b8a1SOliver Brewka inline void handleRequestSystemsLogServiceEventLogEntriesGet( 276*7681b8a1SOliver Brewka const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 277*7681b8a1SOliver Brewka const std::string& param) 278*7681b8a1SOliver Brewka { 279*7681b8a1SOliver Brewka const std::string& targetID = param; 280*7681b8a1SOliver Brewka 281*7681b8a1SOliver Brewka // Go through the log files and check the unique ID for each 282*7681b8a1SOliver Brewka // entry to find the target entry 283*7681b8a1SOliver Brewka std::vector<std::filesystem::path> redfishLogFiles; 284*7681b8a1SOliver Brewka getRedfishLogFiles(redfishLogFiles); 285*7681b8a1SOliver Brewka std::string logEntry; 286*7681b8a1SOliver Brewka 287*7681b8a1SOliver Brewka // Oldest logs are in the last file, so start there and loop 288*7681b8a1SOliver Brewka // backwards 289*7681b8a1SOliver Brewka for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++) 290*7681b8a1SOliver Brewka { 291*7681b8a1SOliver Brewka std::ifstream logStream(*it); 292*7681b8a1SOliver Brewka if (!logStream.is_open()) 293*7681b8a1SOliver Brewka { 294*7681b8a1SOliver Brewka continue; 295*7681b8a1SOliver Brewka } 296*7681b8a1SOliver Brewka 297*7681b8a1SOliver Brewka // Reset the unique ID on the first entry 298*7681b8a1SOliver Brewka bool firstEntry = true; 299*7681b8a1SOliver Brewka while (std::getline(logStream, logEntry)) 300*7681b8a1SOliver Brewka { 301*7681b8a1SOliver Brewka std::string idStr; 302*7681b8a1SOliver Brewka if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 303*7681b8a1SOliver Brewka { 304*7681b8a1SOliver Brewka continue; 305*7681b8a1SOliver Brewka } 306*7681b8a1SOliver Brewka firstEntry = false; 307*7681b8a1SOliver Brewka 308*7681b8a1SOliver Brewka if (idStr == targetID) 309*7681b8a1SOliver Brewka { 310*7681b8a1SOliver Brewka nlohmann::json::object_t bmcLogEntry; 311*7681b8a1SOliver Brewka LogParseError status = 312*7681b8a1SOliver Brewka fillEventLogEntryJson(idStr, logEntry, bmcLogEntry); 313*7681b8a1SOliver Brewka if (status != LogParseError::success) 314*7681b8a1SOliver Brewka { 315*7681b8a1SOliver Brewka messages::internalError(asyncResp->res); 316*7681b8a1SOliver Brewka return; 317*7681b8a1SOliver Brewka } 318*7681b8a1SOliver Brewka asyncResp->res.jsonValue.update(bmcLogEntry); 319*7681b8a1SOliver Brewka return; 320*7681b8a1SOliver Brewka } 321*7681b8a1SOliver Brewka } 322*7681b8a1SOliver Brewka } 323*7681b8a1SOliver Brewka // Requested ID was not found 324*7681b8a1SOliver Brewka messages::resourceNotFound(asyncResp->res, "LogEntry", targetID); 325*7681b8a1SOliver Brewka } 326*7681b8a1SOliver Brewka 327*7681b8a1SOliver Brewka inline void handleRequestSystemsLogServicesEventLogActionsClearPost( 328*7681b8a1SOliver Brewka const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 329*7681b8a1SOliver Brewka { 330*7681b8a1SOliver Brewka // Clear the EventLog by deleting the log files 331*7681b8a1SOliver Brewka std::vector<std::filesystem::path> redfishLogFiles; 332*7681b8a1SOliver Brewka if (getRedfishLogFiles(redfishLogFiles)) 333*7681b8a1SOliver Brewka { 334*7681b8a1SOliver Brewka for (const std::filesystem::path& file : redfishLogFiles) 335*7681b8a1SOliver Brewka { 336*7681b8a1SOliver Brewka std::error_code ec; 337*7681b8a1SOliver Brewka std::filesystem::remove(file, ec); 338*7681b8a1SOliver Brewka } 339*7681b8a1SOliver Brewka } 340*7681b8a1SOliver Brewka 341*7681b8a1SOliver Brewka // Reload rsyslog so it knows to start new log files 342*7681b8a1SOliver Brewka dbus::utility::async_method_call( 343*7681b8a1SOliver Brewka asyncResp, 344*7681b8a1SOliver Brewka [asyncResp](const boost::system::error_code& ec) { 345*7681b8a1SOliver Brewka if (ec) 346*7681b8a1SOliver Brewka { 347*7681b8a1SOliver Brewka BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec); 348*7681b8a1SOliver Brewka messages::internalError(asyncResp->res); 349*7681b8a1SOliver Brewka return; 350*7681b8a1SOliver Brewka } 351*7681b8a1SOliver Brewka 352*7681b8a1SOliver Brewka messages::success(asyncResp->res); 353*7681b8a1SOliver Brewka }, 354*7681b8a1SOliver Brewka "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 355*7681b8a1SOliver Brewka "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service", 356*7681b8a1SOliver Brewka "replace"); 357*7681b8a1SOliver Brewka } 358*7681b8a1SOliver Brewka } // namespace eventlog_utils 359*7681b8a1SOliver Brewka } // namespace redfish 360