1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "node.hpp" 19 #include "registries.hpp" 20 #include "registries/base_message_registry.hpp" 21 #include "registries/openbmc_message_registry.hpp" 22 23 #include <systemd/sd-journal.h> 24 25 #include <boost/algorithm/string/split.hpp> 26 #include <boost/beast/core/span.hpp> 27 #include <boost/container/flat_map.hpp> 28 #include <error_messages.hpp> 29 #include <filesystem> 30 #include <string_view> 31 #include <variant> 32 33 namespace redfish 34 { 35 36 constexpr char const *CrashdumpObject = "com.intel.crashdump"; 37 constexpr char const *CrashdumpPath = "/com/intel/crashdump"; 38 constexpr char const *CrashdumpOnDemandPath = "/com/intel/crashdump/OnDemand"; 39 constexpr char const *CrashdumpInterface = "com.intel.crashdump"; 40 constexpr char const *CrashdumpOnDemandInterface = 41 "com.intel.crashdump.OnDemand"; 42 constexpr char const *CrashdumpRawPECIInterface = 43 "com.intel.crashdump.SendRawPeci"; 44 45 namespace message_registries 46 { 47 static const Message *getMessageFromRegistry( 48 const std::string &messageKey, 49 const boost::beast::span<const MessageEntry> registry) 50 { 51 boost::beast::span<const MessageEntry>::const_iterator messageIt = 52 std::find_if(registry.cbegin(), registry.cend(), 53 [&messageKey](const MessageEntry &messageEntry) { 54 return !std::strcmp(messageEntry.first, 55 messageKey.c_str()); 56 }); 57 if (messageIt != registry.cend()) 58 { 59 return &messageIt->second; 60 } 61 62 return nullptr; 63 } 64 65 static const Message *getMessage(const std::string_view &messageID) 66 { 67 // Redfish MessageIds are in the form 68 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 69 // the right Message 70 std::vector<std::string> fields; 71 fields.reserve(4); 72 boost::split(fields, messageID, boost::is_any_of(".")); 73 std::string ®istryName = fields[0]; 74 std::string &messageKey = fields[3]; 75 76 // Find the right registry and check it for the MessageKey 77 if (std::string(base::header.registryPrefix) == registryName) 78 { 79 return getMessageFromRegistry( 80 messageKey, boost::beast::span<const MessageEntry>(base::registry)); 81 } 82 if (std::string(openbmc::header.registryPrefix) == registryName) 83 { 84 return getMessageFromRegistry( 85 messageKey, 86 boost::beast::span<const MessageEntry>(openbmc::registry)); 87 } 88 return nullptr; 89 } 90 } // namespace message_registries 91 92 namespace fs = std::filesystem; 93 94 using GetManagedPropertyType = boost::container::flat_map< 95 std::string, 96 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t, 97 int32_t, uint32_t, int64_t, uint64_t, double>>; 98 99 using GetManagedObjectsType = boost::container::flat_map< 100 sdbusplus::message::object_path, 101 boost::container::flat_map<std::string, GetManagedPropertyType>>; 102 103 inline std::string translateSeverityDbusToRedfish(const std::string &s) 104 { 105 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert") 106 { 107 return "Critical"; 108 } 109 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") 110 { 111 return "Critical"; 112 } 113 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug") 114 { 115 return "OK"; 116 } 117 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") 118 { 119 return "Critical"; 120 } 121 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error") 122 { 123 return "Critical"; 124 } 125 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") 126 { 127 return "OK"; 128 } 129 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice") 130 { 131 return "OK"; 132 } 133 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") 134 { 135 return "Warning"; 136 } 137 return ""; 138 } 139 140 static int getJournalMetadata(sd_journal *journal, 141 const std::string_view &field, 142 std::string_view &contents) 143 { 144 const char *data = nullptr; 145 size_t length = 0; 146 int ret = 0; 147 // Get the metadata from the requested field of the journal entry 148 ret = sd_journal_get_data(journal, field.data(), 149 reinterpret_cast<const void **>(&data), &length); 150 if (ret < 0) 151 { 152 return ret; 153 } 154 contents = std::string_view(data, length); 155 // Only use the content after the "=" character. 156 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 157 return ret; 158 } 159 160 static int getJournalMetadata(sd_journal *journal, 161 const std::string_view &field, const int &base, 162 long int &contents) 163 { 164 int ret = 0; 165 std::string_view metadata; 166 // Get the metadata from the requested field of the journal entry 167 ret = getJournalMetadata(journal, field, metadata); 168 if (ret < 0) 169 { 170 return ret; 171 } 172 contents = strtol(metadata.data(), nullptr, base); 173 return ret; 174 } 175 176 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 177 { 178 int ret = 0; 179 uint64_t timestamp = 0; 180 ret = sd_journal_get_realtime_usec(journal, ×tamp); 181 if (ret < 0) 182 { 183 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 184 << strerror(-ret); 185 return false; 186 } 187 time_t t = 188 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s 189 struct tm *loctime = localtime(&t); 190 char entryTime[64] = {}; 191 if (NULL != loctime) 192 { 193 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 194 } 195 // Insert the ':' into the timezone 196 std::string_view t1(entryTime); 197 std::string_view t2(entryTime); 198 if (t1.size() > 2 && t2.size() > 2) 199 { 200 t1.remove_suffix(2); 201 t2.remove_prefix(t2.size() - 2); 202 } 203 entryTimestamp = std::string(t1) + ":" + std::string(t2); 204 return true; 205 } 206 207 static bool getSkipParam(crow::Response &res, const crow::Request &req, 208 uint64_t &skip) 209 { 210 char *skipParam = req.urlParams.get("$skip"); 211 if (skipParam != nullptr) 212 { 213 char *ptr = nullptr; 214 skip = std::strtoul(skipParam, &ptr, 10); 215 if (*skipParam == '\0' || *ptr != '\0') 216 { 217 218 messages::queryParameterValueTypeError(res, std::string(skipParam), 219 "$skip"); 220 return false; 221 } 222 } 223 return true; 224 } 225 226 static constexpr const uint64_t maxEntriesPerPage = 1000; 227 static bool getTopParam(crow::Response &res, const crow::Request &req, 228 uint64_t &top) 229 { 230 char *topParam = req.urlParams.get("$top"); 231 if (topParam != nullptr) 232 { 233 char *ptr = nullptr; 234 top = std::strtoul(topParam, &ptr, 10); 235 if (*topParam == '\0' || *ptr != '\0') 236 { 237 messages::queryParameterValueTypeError(res, std::string(topParam), 238 "$top"); 239 return false; 240 } 241 if (top < 1U || top > maxEntriesPerPage) 242 { 243 244 messages::queryParameterOutOfRange( 245 res, std::to_string(top), "$top", 246 "1-" + std::to_string(maxEntriesPerPage)); 247 return false; 248 } 249 } 250 return true; 251 } 252 253 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID, 254 const bool firstEntry = true) 255 { 256 int ret = 0; 257 static uint64_t prevTs = 0; 258 static int index = 0; 259 if (firstEntry) 260 { 261 prevTs = 0; 262 } 263 264 // Get the entry timestamp 265 uint64_t curTs = 0; 266 ret = sd_journal_get_realtime_usec(journal, &curTs); 267 if (ret < 0) 268 { 269 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 270 << strerror(-ret); 271 return false; 272 } 273 // If the timestamp isn't unique, increment the index 274 if (curTs == prevTs) 275 { 276 index++; 277 } 278 else 279 { 280 // Otherwise, reset it 281 index = 0; 282 } 283 // Save the timestamp 284 prevTs = curTs; 285 286 entryID = std::to_string(curTs); 287 if (index > 0) 288 { 289 entryID += "_" + std::to_string(index); 290 } 291 return true; 292 } 293 294 static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID, 295 const bool firstEntry = true) 296 { 297 static time_t prevTs = 0; 298 static int index = 0; 299 if (firstEntry) 300 { 301 prevTs = 0; 302 } 303 304 // Get the entry timestamp 305 std::time_t curTs = 0; 306 std::tm timeStruct = {}; 307 std::istringstream entryStream(logEntry); 308 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 309 { 310 curTs = std::mktime(&timeStruct); 311 } 312 // If the timestamp isn't unique, increment the index 313 if (curTs == prevTs) 314 { 315 index++; 316 } 317 else 318 { 319 // Otherwise, reset it 320 index = 0; 321 } 322 // Save the timestamp 323 prevTs = curTs; 324 325 entryID = std::to_string(curTs); 326 if (index > 0) 327 { 328 entryID += "_" + std::to_string(index); 329 } 330 return true; 331 } 332 333 static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 334 uint64_t ×tamp, uint64_t &index) 335 { 336 if (entryID.empty()) 337 { 338 return false; 339 } 340 // Convert the unique ID back to a timestamp to find the entry 341 std::string_view tsStr(entryID); 342 343 auto underscorePos = tsStr.find("_"); 344 if (underscorePos != tsStr.npos) 345 { 346 // Timestamp has an index 347 tsStr.remove_suffix(tsStr.size() - underscorePos); 348 std::string_view indexStr(entryID); 349 indexStr.remove_prefix(underscorePos + 1); 350 std::size_t pos; 351 try 352 { 353 index = std::stoul(std::string(indexStr), &pos); 354 } 355 catch (std::invalid_argument &) 356 { 357 messages::resourceMissingAtURI(res, entryID); 358 return false; 359 } 360 catch (std::out_of_range &) 361 { 362 messages::resourceMissingAtURI(res, entryID); 363 return false; 364 } 365 if (pos != indexStr.size()) 366 { 367 messages::resourceMissingAtURI(res, entryID); 368 return false; 369 } 370 } 371 // Timestamp has no index 372 std::size_t pos; 373 try 374 { 375 timestamp = std::stoull(std::string(tsStr), &pos); 376 } 377 catch (std::invalid_argument &) 378 { 379 messages::resourceMissingAtURI(res, entryID); 380 return false; 381 } 382 catch (std::out_of_range &) 383 { 384 messages::resourceMissingAtURI(res, entryID); 385 return false; 386 } 387 if (pos != tsStr.size()) 388 { 389 messages::resourceMissingAtURI(res, entryID); 390 return false; 391 } 392 return true; 393 } 394 395 static bool 396 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles) 397 { 398 static const std::filesystem::path redfishLogDir = "/var/log"; 399 static const std::string redfishLogFilename = "redfish"; 400 401 // Loop through the directory looking for redfish log files 402 for (const std::filesystem::directory_entry &dirEnt : 403 std::filesystem::directory_iterator(redfishLogDir)) 404 { 405 // If we find a redfish log file, save the path 406 std::string filename = dirEnt.path().filename(); 407 if (boost::starts_with(filename, redfishLogFilename)) 408 { 409 redfishLogFiles.emplace_back(redfishLogDir / filename); 410 } 411 } 412 // As the log files rotate, they are appended with a ".#" that is higher for 413 // the older logs. Since we don't expect more than 10 log files, we 414 // can just sort the list to get them in order from newest to oldest 415 std::sort(redfishLogFiles.begin(), redfishLogFiles.end()); 416 417 return !redfishLogFiles.empty(); 418 } 419 420 class SystemLogServiceCollection : public Node 421 { 422 public: 423 template <typename CrowApp> 424 SystemLogServiceCollection(CrowApp &app) : 425 Node(app, "/redfish/v1/Systems/system/LogServices/") 426 { 427 entityPrivileges = { 428 {boost::beast::http::verb::get, {{"Login"}}}, 429 {boost::beast::http::verb::head, {{"Login"}}}, 430 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 431 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 432 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 433 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 434 } 435 436 private: 437 /** 438 * Functions triggers appropriate requests on DBus 439 */ 440 void doGet(crow::Response &res, const crow::Request &req, 441 const std::vector<std::string> ¶ms) override 442 { 443 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 444 // Collections don't include the static data added by SubRoute because 445 // it has a duplicate entry for members 446 asyncResp->res.jsonValue["@odata.type"] = 447 "#LogServiceCollection.LogServiceCollection"; 448 asyncResp->res.jsonValue["@odata.context"] = 449 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 450 asyncResp->res.jsonValue["@odata.id"] = 451 "/redfish/v1/Systems/system/LogServices"; 452 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 453 asyncResp->res.jsonValue["Description"] = 454 "Collection of LogServices for this Computer System"; 455 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 456 logServiceArray = nlohmann::json::array(); 457 logServiceArray.push_back( 458 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); 459 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 460 logServiceArray.push_back( 461 {{"@odata.id", 462 "/redfish/v1/Systems/system/LogServices/Crashdump"}}); 463 #endif 464 asyncResp->res.jsonValue["Members@odata.count"] = 465 logServiceArray.size(); 466 } 467 }; 468 469 class EventLogService : public Node 470 { 471 public: 472 template <typename CrowApp> 473 EventLogService(CrowApp &app) : 474 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 475 { 476 entityPrivileges = { 477 {boost::beast::http::verb::get, {{"Login"}}}, 478 {boost::beast::http::verb::head, {{"Login"}}}, 479 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 480 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 481 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 482 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 483 } 484 485 private: 486 void doGet(crow::Response &res, const crow::Request &req, 487 const std::vector<std::string> ¶ms) override 488 { 489 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 490 491 asyncResp->res.jsonValue["@odata.id"] = 492 "/redfish/v1/Systems/system/LogServices/EventLog"; 493 asyncResp->res.jsonValue["@odata.type"] = 494 "#LogService.v1_1_0.LogService"; 495 asyncResp->res.jsonValue["@odata.context"] = 496 "/redfish/v1/$metadata#LogService.LogService"; 497 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 498 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 499 asyncResp->res.jsonValue["Id"] = "Event Log"; 500 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 501 asyncResp->res.jsonValue["Entries"] = { 502 {"@odata.id", 503 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 504 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 505 506 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/" 507 "Actions/LogService.ClearLog"}}; 508 } 509 }; 510 511 class EventLogClear : public Node 512 { 513 public: 514 EventLogClear(CrowApp &app) : 515 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 516 "LogService.ClearLog/") 517 { 518 entityPrivileges = { 519 {boost::beast::http::verb::get, {{"Login"}}}, 520 {boost::beast::http::verb::head, {{"Login"}}}, 521 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 522 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 523 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 524 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 525 } 526 527 private: 528 void doPost(crow::Response &res, const crow::Request &req, 529 const std::vector<std::string> ¶ms) override 530 { 531 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 532 533 // Clear the EventLog by deleting the log files 534 std::vector<std::filesystem::path> redfishLogFiles; 535 if (getRedfishLogFiles(redfishLogFiles)) 536 { 537 for (const std::filesystem::path &file : redfishLogFiles) 538 { 539 std::error_code ec; 540 std::filesystem::remove(file, ec); 541 } 542 } 543 544 // Reload rsyslog so it knows to start new log files 545 crow::connections::systemBus->async_method_call( 546 [asyncResp](const boost::system::error_code ec) { 547 if (ec) 548 { 549 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec; 550 messages::internalError(asyncResp->res); 551 return; 552 } 553 554 messages::success(asyncResp->res); 555 }, 556 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 557 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service", 558 "replace"); 559 } 560 }; 561 562 static int fillEventLogEntryJson(const std::string &logEntryID, 563 const std::string logEntry, 564 nlohmann::json &logEntryJson) 565 { 566 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 567 // First get the Timestamp 568 size_t space = logEntry.find_first_of(" "); 569 if (space == std::string::npos) 570 { 571 return 1; 572 } 573 std::string timestamp = logEntry.substr(0, space); 574 // Then get the log contents 575 size_t entryStart = logEntry.find_first_not_of(" ", space); 576 if (entryStart == std::string::npos) 577 { 578 return 1; 579 } 580 std::string_view entry(logEntry); 581 entry.remove_prefix(entryStart); 582 // Use split to separate the entry into its fields 583 std::vector<std::string> logEntryFields; 584 boost::split(logEntryFields, entry, boost::is_any_of(","), 585 boost::token_compress_on); 586 // We need at least a MessageId to be valid 587 if (logEntryFields.size() < 1) 588 { 589 return 1; 590 } 591 std::string &messageID = logEntryFields[0]; 592 593 // Get the Message from the MessageRegistry 594 const message_registries::Message *message = 595 message_registries::getMessage(messageID); 596 597 std::string msg; 598 std::string severity; 599 if (message != nullptr) 600 { 601 msg = message->message; 602 severity = message->severity; 603 } 604 605 // Get the MessageArgs from the log if there are any 606 boost::beast::span<std::string> messageArgs; 607 if (logEntryFields.size() > 1) 608 { 609 std::string &messageArgsStart = logEntryFields[1]; 610 // If the first string is empty, assume there are no MessageArgs 611 std::size_t messageArgsSize = 0; 612 if (!messageArgsStart.empty()) 613 { 614 messageArgsSize = logEntryFields.size() - 1; 615 } 616 617 messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize); 618 619 // Fill the MessageArgs into the Message 620 int i = 0; 621 for (const std::string &messageArg : messageArgs) 622 { 623 std::string argStr = "%" + std::to_string(++i); 624 size_t argPos = msg.find(argStr); 625 if (argPos != std::string::npos) 626 { 627 msg.replace(argPos, argStr.length(), messageArg); 628 } 629 } 630 } 631 632 // Get the Created time from the timestamp. The log timestamp is in RFC3339 633 // format which matches the Redfish format except for the fractional seconds 634 // between the '.' and the '+', so just remove them. 635 std::size_t dot = timestamp.find_first_of("."); 636 std::size_t plus = timestamp.find_first_of("+"); 637 if (dot != std::string::npos && plus != std::string::npos) 638 { 639 timestamp.erase(dot, plus - dot); 640 } 641 642 // Fill in the log entry with the gathered data 643 logEntryJson = { 644 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 645 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 646 {"@odata.id", 647 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 648 logEntryID}, 649 {"Name", "System Event Log Entry"}, 650 {"Id", logEntryID}, 651 {"Message", std::move(msg)}, 652 {"MessageId", std::move(messageID)}, 653 {"MessageArgs", std::move(messageArgs)}, 654 {"EntryType", "Event"}, 655 {"Severity", std::move(severity)}, 656 {"Created", std::move(timestamp)}}; 657 return 0; 658 } 659 660 class JournalEventLogEntryCollection : public Node 661 { 662 public: 663 template <typename CrowApp> 664 JournalEventLogEntryCollection(CrowApp &app) : 665 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 666 { 667 entityPrivileges = { 668 {boost::beast::http::verb::get, {{"Login"}}}, 669 {boost::beast::http::verb::head, {{"Login"}}}, 670 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 671 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 672 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 673 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 674 } 675 676 private: 677 void doGet(crow::Response &res, const crow::Request &req, 678 const std::vector<std::string> ¶ms) override 679 { 680 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 681 uint64_t skip = 0; 682 uint64_t top = maxEntriesPerPage; // Show max entries by default 683 if (!getSkipParam(asyncResp->res, req, skip)) 684 { 685 return; 686 } 687 if (!getTopParam(asyncResp->res, req, top)) 688 { 689 return; 690 } 691 // Collections don't include the static data added by SubRoute because 692 // it has a duplicate entry for members 693 asyncResp->res.jsonValue["@odata.type"] = 694 "#LogEntryCollection.LogEntryCollection"; 695 asyncResp->res.jsonValue["@odata.context"] = 696 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 697 asyncResp->res.jsonValue["@odata.id"] = 698 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 699 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 700 asyncResp->res.jsonValue["Description"] = 701 "Collection of System Event Log Entries"; 702 703 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 704 logEntryArray = nlohmann::json::array(); 705 // Go through the log files and create a unique ID for each entry 706 std::vector<std::filesystem::path> redfishLogFiles; 707 getRedfishLogFiles(redfishLogFiles); 708 uint64_t entryCount = 0; 709 std::string logEntry; 710 711 // Oldest logs are in the last file, so start there and loop backwards 712 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 713 it++) 714 { 715 std::ifstream logStream(*it); 716 if (!logStream.is_open()) 717 { 718 continue; 719 } 720 721 // Reset the unique ID on the first entry 722 bool firstEntry = true; 723 while (std::getline(logStream, logEntry)) 724 { 725 entryCount++; 726 // Handle paging using skip (number of entries to skip from the 727 // start) and top (number of entries to display) 728 if (entryCount <= skip || entryCount > skip + top) 729 { 730 continue; 731 } 732 733 std::string idStr; 734 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 735 { 736 continue; 737 } 738 739 if (firstEntry) 740 { 741 firstEntry = false; 742 } 743 744 logEntryArray.push_back({}); 745 nlohmann::json &bmcLogEntry = logEntryArray.back(); 746 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0) 747 { 748 messages::internalError(asyncResp->res); 749 return; 750 } 751 } 752 } 753 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 754 if (skip + top < entryCount) 755 { 756 asyncResp->res.jsonValue["Members@odata.nextLink"] = 757 "/redfish/v1/Systems/system/LogServices/EventLog/" 758 "Entries?$skip=" + 759 std::to_string(skip + top); 760 } 761 } 762 }; 763 764 class JournalEventLogEntry : public Node 765 { 766 public: 767 JournalEventLogEntry(CrowApp &app) : 768 Node(app, 769 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 770 std::string()) 771 { 772 entityPrivileges = { 773 {boost::beast::http::verb::get, {{"Login"}}}, 774 {boost::beast::http::verb::head, {{"Login"}}}, 775 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 776 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 777 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 778 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 779 } 780 781 private: 782 void doGet(crow::Response &res, const crow::Request &req, 783 const std::vector<std::string> ¶ms) override 784 { 785 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 786 if (params.size() != 1) 787 { 788 messages::internalError(asyncResp->res); 789 return; 790 } 791 const std::string &targetID = params[0]; 792 793 // Go through the log files and check the unique ID for each entry to 794 // find the target entry 795 std::vector<std::filesystem::path> redfishLogFiles; 796 getRedfishLogFiles(redfishLogFiles); 797 std::string logEntry; 798 799 // Oldest logs are in the last file, so start there and loop backwards 800 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 801 it++) 802 { 803 std::ifstream logStream(*it); 804 if (!logStream.is_open()) 805 { 806 continue; 807 } 808 809 // Reset the unique ID on the first entry 810 bool firstEntry = true; 811 while (std::getline(logStream, logEntry)) 812 { 813 std::string idStr; 814 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 815 { 816 continue; 817 } 818 819 if (firstEntry) 820 { 821 firstEntry = false; 822 } 823 824 if (idStr == targetID) 825 { 826 if (fillEventLogEntryJson(idStr, logEntry, 827 asyncResp->res.jsonValue) != 0) 828 { 829 messages::internalError(asyncResp->res); 830 return; 831 } 832 return; 833 } 834 } 835 } 836 // Requested ID was not found 837 messages::resourceMissingAtURI(asyncResp->res, targetID); 838 } 839 }; 840 841 class DBusEventLogEntryCollection : public Node 842 { 843 public: 844 template <typename CrowApp> 845 DBusEventLogEntryCollection(CrowApp &app) : 846 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 847 { 848 entityPrivileges = { 849 {boost::beast::http::verb::get, {{"Login"}}}, 850 {boost::beast::http::verb::head, {{"Login"}}}, 851 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 852 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 853 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 854 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 855 } 856 857 private: 858 void doGet(crow::Response &res, const crow::Request &req, 859 const std::vector<std::string> ¶ms) override 860 { 861 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 862 863 // Collections don't include the static data added by SubRoute because 864 // it has a duplicate entry for members 865 asyncResp->res.jsonValue["@odata.type"] = 866 "#LogEntryCollection.LogEntryCollection"; 867 asyncResp->res.jsonValue["@odata.context"] = 868 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 869 asyncResp->res.jsonValue["@odata.id"] = 870 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 871 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 872 asyncResp->res.jsonValue["Description"] = 873 "Collection of System Event Log Entries"; 874 875 // DBus implementation of EventLog/Entries 876 // Make call to Logging Service to find all log entry objects 877 crow::connections::systemBus->async_method_call( 878 [asyncResp](const boost::system::error_code ec, 879 GetManagedObjectsType &resp) { 880 if (ec) 881 { 882 // TODO Handle for specific error code 883 BMCWEB_LOG_ERROR 884 << "getLogEntriesIfaceData resp_handler got error " 885 << ec; 886 messages::internalError(asyncResp->res); 887 return; 888 } 889 nlohmann::json &entriesArray = 890 asyncResp->res.jsonValue["Members"]; 891 entriesArray = nlohmann::json::array(); 892 for (auto &objectPath : resp) 893 { 894 for (auto &interfaceMap : objectPath.second) 895 { 896 if (interfaceMap.first != 897 "xyz.openbmc_project.Logging.Entry") 898 { 899 BMCWEB_LOG_DEBUG << "Bailing early on " 900 << interfaceMap.first; 901 continue; 902 } 903 entriesArray.push_back({}); 904 nlohmann::json &thisEntry = entriesArray.back(); 905 uint32_t *id = nullptr; 906 std::time_t timestamp{}; 907 std::string *severity = nullptr; 908 std::string *message = nullptr; 909 for (auto &propertyMap : interfaceMap.second) 910 { 911 if (propertyMap.first == "Id") 912 { 913 id = sdbusplus::message::variant_ns::get_if< 914 uint32_t>(&propertyMap.second); 915 if (id == nullptr) 916 { 917 messages::propertyMissing(asyncResp->res, 918 "Id"); 919 } 920 } 921 else if (propertyMap.first == "Timestamp") 922 { 923 const uint64_t *millisTimeStamp = 924 std::get_if<uint64_t>(&propertyMap.second); 925 if (millisTimeStamp == nullptr) 926 { 927 messages::propertyMissing(asyncResp->res, 928 "Timestamp"); 929 continue; 930 } 931 // Retrieve Created property with format: 932 // yyyy-mm-ddThh:mm:ss 933 std::chrono::milliseconds chronoTimeStamp( 934 *millisTimeStamp); 935 timestamp = std::chrono::duration_cast< 936 std::chrono::duration<int>>( 937 chronoTimeStamp) 938 .count(); 939 } 940 else if (propertyMap.first == "Severity") 941 { 942 severity = std::get_if<std::string>( 943 &propertyMap.second); 944 if (severity == nullptr) 945 { 946 messages::propertyMissing(asyncResp->res, 947 "Severity"); 948 } 949 } 950 else if (propertyMap.first == "Message") 951 { 952 message = std::get_if<std::string>( 953 &propertyMap.second); 954 if (message == nullptr) 955 { 956 messages::propertyMissing(asyncResp->res, 957 "Message"); 958 } 959 } 960 } 961 thisEntry = { 962 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 963 {"@odata.context", "/redfish/v1/" 964 "$metadata#LogEntry.LogEntry"}, 965 {"@odata.id", 966 "/redfish/v1/Systems/system/LogServices/EventLog/" 967 "Entries/" + 968 std::to_string(*id)}, 969 {"Name", "System Event Log Entry"}, 970 {"Id", std::to_string(*id)}, 971 {"Message", *message}, 972 {"EntryType", "Event"}, 973 {"Severity", 974 translateSeverityDbusToRedfish(*severity)}, 975 {"Created", crow::utility::getDateTime(timestamp)}}; 976 } 977 } 978 std::sort(entriesArray.begin(), entriesArray.end(), 979 [](const nlohmann::json &left, 980 const nlohmann::json &right) { 981 return (left["Id"] <= right["Id"]); 982 }); 983 asyncResp->res.jsonValue["Members@odata.count"] = 984 entriesArray.size(); 985 }, 986 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 987 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 988 } 989 }; 990 991 class DBusEventLogEntry : public Node 992 { 993 public: 994 DBusEventLogEntry(CrowApp &app) : 995 Node(app, 996 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 997 std::string()) 998 { 999 entityPrivileges = { 1000 {boost::beast::http::verb::get, {{"Login"}}}, 1001 {boost::beast::http::verb::head, {{"Login"}}}, 1002 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1003 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1004 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1005 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1006 } 1007 1008 private: 1009 void doGet(crow::Response &res, const crow::Request &req, 1010 const std::vector<std::string> ¶ms) override 1011 { 1012 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1013 if (params.size() != 1) 1014 { 1015 messages::internalError(asyncResp->res); 1016 return; 1017 } 1018 const std::string &entryID = params[0]; 1019 1020 // DBus implementation of EventLog/Entries 1021 // Make call to Logging Service to find all log entry objects 1022 crow::connections::systemBus->async_method_call( 1023 [asyncResp, entryID](const boost::system::error_code ec, 1024 GetManagedPropertyType &resp) { 1025 if (ec) 1026 { 1027 BMCWEB_LOG_ERROR 1028 << "EventLogEntry (DBus) resp_handler got error " << ec; 1029 messages::internalError(asyncResp->res); 1030 return; 1031 } 1032 uint32_t *id = nullptr; 1033 std::time_t timestamp{}; 1034 std::string *severity = nullptr; 1035 std::string *message = nullptr; 1036 for (auto &propertyMap : resp) 1037 { 1038 if (propertyMap.first == "Id") 1039 { 1040 id = std::get_if<uint32_t>(&propertyMap.second); 1041 if (id == nullptr) 1042 { 1043 messages::propertyMissing(asyncResp->res, "Id"); 1044 } 1045 } 1046 else if (propertyMap.first == "Timestamp") 1047 { 1048 const uint64_t *millisTimeStamp = 1049 std::get_if<uint64_t>(&propertyMap.second); 1050 if (millisTimeStamp == nullptr) 1051 { 1052 messages::propertyMissing(asyncResp->res, 1053 "Timestamp"); 1054 continue; 1055 } 1056 // Retrieve Created property with format: 1057 // yyyy-mm-ddThh:mm:ss 1058 std::chrono::milliseconds chronoTimeStamp( 1059 *millisTimeStamp); 1060 timestamp = 1061 std::chrono::duration_cast< 1062 std::chrono::duration<int>>(chronoTimeStamp) 1063 .count(); 1064 } 1065 else if (propertyMap.first == "Severity") 1066 { 1067 severity = 1068 std::get_if<std::string>(&propertyMap.second); 1069 if (severity == nullptr) 1070 { 1071 messages::propertyMissing(asyncResp->res, 1072 "Severity"); 1073 } 1074 } 1075 else if (propertyMap.first == "Message") 1076 { 1077 message = std::get_if<std::string>(&propertyMap.second); 1078 if (message == nullptr) 1079 { 1080 messages::propertyMissing(asyncResp->res, 1081 "Message"); 1082 } 1083 } 1084 } 1085 if (id == nullptr || message == nullptr || severity == nullptr) 1086 { 1087 return; 1088 } 1089 asyncResp->res.jsonValue = { 1090 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1091 {"@odata.context", "/redfish/v1/" 1092 "$metadata#LogEntry.LogEntry"}, 1093 {"@odata.id", 1094 "/redfish/v1/Systems/system/LogServices/EventLog/" 1095 "Entries/" + 1096 std::to_string(*id)}, 1097 {"Name", "System Event Log Entry"}, 1098 {"Id", std::to_string(*id)}, 1099 {"Message", *message}, 1100 {"EntryType", "Event"}, 1101 {"Severity", translateSeverityDbusToRedfish(*severity)}, 1102 {"Created", crow::utility::getDateTime(timestamp)}}; 1103 }, 1104 "xyz.openbmc_project.Logging", 1105 "/xyz/openbmc_project/logging/entry/" + entryID, 1106 "org.freedesktop.DBus.Properties", "GetAll", 1107 "xyz.openbmc_project.Logging.Entry"); 1108 } 1109 1110 void doDelete(crow::Response &res, const crow::Request &req, 1111 const std::vector<std::string> ¶ms) override 1112 { 1113 1114 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1115 1116 auto asyncResp = std::make_shared<AsyncResp>(res); 1117 1118 if (params.size() != 1) 1119 { 1120 messages::internalError(asyncResp->res); 1121 return; 1122 } 1123 std::string entryID = params[0]; 1124 1125 dbus::utility::escapePathForDbus(entryID); 1126 1127 // Process response from Logging service. 1128 auto respHandler = [asyncResp](const boost::system::error_code ec) { 1129 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done"; 1130 if (ec) 1131 { 1132 // TODO Handle for specific error code 1133 BMCWEB_LOG_ERROR 1134 << "EventLogEntry (DBus) doDelete respHandler got error " 1135 << ec; 1136 asyncResp->res.result( 1137 boost::beast::http::status::internal_server_error); 1138 return; 1139 } 1140 1141 asyncResp->res.result(boost::beast::http::status::ok); 1142 }; 1143 1144 // Make call to Logging service to request Delete Log 1145 crow::connections::systemBus->async_method_call( 1146 respHandler, "xyz.openbmc_project.Logging", 1147 "/xyz/openbmc_project/logging/entry/" + entryID, 1148 "xyz.openbmc_project.Object.Delete", "Delete"); 1149 } 1150 }; 1151 1152 class BMCLogServiceCollection : public Node 1153 { 1154 public: 1155 template <typename CrowApp> 1156 BMCLogServiceCollection(CrowApp &app) : 1157 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 1158 { 1159 entityPrivileges = { 1160 {boost::beast::http::verb::get, {{"Login"}}}, 1161 {boost::beast::http::verb::head, {{"Login"}}}, 1162 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1163 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1164 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1165 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1166 } 1167 1168 private: 1169 /** 1170 * Functions triggers appropriate requests on DBus 1171 */ 1172 void doGet(crow::Response &res, const crow::Request &req, 1173 const std::vector<std::string> ¶ms) override 1174 { 1175 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1176 // Collections don't include the static data added by SubRoute because 1177 // it has a duplicate entry for members 1178 asyncResp->res.jsonValue["@odata.type"] = 1179 "#LogServiceCollection.LogServiceCollection"; 1180 asyncResp->res.jsonValue["@odata.context"] = 1181 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 1182 asyncResp->res.jsonValue["@odata.id"] = 1183 "/redfish/v1/Managers/bmc/LogServices"; 1184 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 1185 asyncResp->res.jsonValue["Description"] = 1186 "Collection of LogServices for this Manager"; 1187 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 1188 logServiceArray = nlohmann::json::array(); 1189 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1190 logServiceArray.push_back( 1191 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 1192 #endif 1193 asyncResp->res.jsonValue["Members@odata.count"] = 1194 logServiceArray.size(); 1195 } 1196 }; 1197 1198 class BMCJournalLogService : public Node 1199 { 1200 public: 1201 template <typename CrowApp> 1202 BMCJournalLogService(CrowApp &app) : 1203 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1204 { 1205 entityPrivileges = { 1206 {boost::beast::http::verb::get, {{"Login"}}}, 1207 {boost::beast::http::verb::head, {{"Login"}}}, 1208 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1209 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1210 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1211 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1212 } 1213 1214 private: 1215 void doGet(crow::Response &res, const crow::Request &req, 1216 const std::vector<std::string> ¶ms) override 1217 { 1218 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1219 asyncResp->res.jsonValue["@odata.type"] = 1220 "#LogService.v1_1_0.LogService"; 1221 asyncResp->res.jsonValue["@odata.id"] = 1222 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1223 asyncResp->res.jsonValue["@odata.context"] = 1224 "/redfish/v1/$metadata#LogService.LogService"; 1225 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 1226 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 1227 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1228 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1229 asyncResp->res.jsonValue["Entries"] = { 1230 {"@odata.id", 1231 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1232 } 1233 }; 1234 1235 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 1236 sd_journal *journal, 1237 nlohmann::json &bmcJournalLogEntryJson) 1238 { 1239 // Get the Log Entry contents 1240 int ret = 0; 1241 1242 std::string_view msg; 1243 ret = getJournalMetadata(journal, "MESSAGE", msg); 1244 if (ret < 0) 1245 { 1246 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1247 return 1; 1248 } 1249 1250 // Get the severity from the PRIORITY field 1251 long int severity = 8; // Default to an invalid priority 1252 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1253 if (ret < 0) 1254 { 1255 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1256 } 1257 1258 // Get the Created time from the timestamp 1259 std::string entryTimeStr; 1260 if (!getEntryTimestamp(journal, entryTimeStr)) 1261 { 1262 return 1; 1263 } 1264 1265 // Fill in the log entry with the gathered data 1266 bmcJournalLogEntryJson = { 1267 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1268 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1269 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1270 bmcJournalLogEntryID}, 1271 {"Name", "BMC Journal Entry"}, 1272 {"Id", bmcJournalLogEntryID}, 1273 {"Message", msg}, 1274 {"EntryType", "Oem"}, 1275 {"Severity", 1276 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"}, 1277 {"OemRecordFormat", "BMC Journal Entry"}, 1278 {"Created", std::move(entryTimeStr)}}; 1279 return 0; 1280 } 1281 1282 class BMCJournalLogEntryCollection : public Node 1283 { 1284 public: 1285 template <typename CrowApp> 1286 BMCJournalLogEntryCollection(CrowApp &app) : 1287 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1288 { 1289 entityPrivileges = { 1290 {boost::beast::http::verb::get, {{"Login"}}}, 1291 {boost::beast::http::verb::head, {{"Login"}}}, 1292 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1293 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1294 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1295 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1296 } 1297 1298 private: 1299 void doGet(crow::Response &res, const crow::Request &req, 1300 const std::vector<std::string> ¶ms) override 1301 { 1302 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1303 static constexpr const long maxEntriesPerPage = 1000; 1304 uint64_t skip = 0; 1305 uint64_t top = maxEntriesPerPage; // Show max entries by default 1306 if (!getSkipParam(asyncResp->res, req, skip)) 1307 { 1308 return; 1309 } 1310 if (!getTopParam(asyncResp->res, req, top)) 1311 { 1312 return; 1313 } 1314 // Collections don't include the static data added by SubRoute because 1315 // it has a duplicate entry for members 1316 asyncResp->res.jsonValue["@odata.type"] = 1317 "#LogEntryCollection.LogEntryCollection"; 1318 asyncResp->res.jsonValue["@odata.id"] = 1319 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1320 asyncResp->res.jsonValue["@odata.context"] = 1321 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1322 asyncResp->res.jsonValue["@odata.id"] = 1323 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1324 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1325 asyncResp->res.jsonValue["Description"] = 1326 "Collection of BMC Journal Entries"; 1327 asyncResp->res.jsonValue["@odata.id"] = 1328 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1329 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1330 logEntryArray = nlohmann::json::array(); 1331 1332 // Go through the journal and use the timestamp to create a unique ID 1333 // for each entry 1334 sd_journal *journalTmp = nullptr; 1335 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1336 if (ret < 0) 1337 { 1338 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1339 messages::internalError(asyncResp->res); 1340 return; 1341 } 1342 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1343 journalTmp, sd_journal_close); 1344 journalTmp = nullptr; 1345 uint64_t entryCount = 0; 1346 // Reset the unique ID on the first entry 1347 bool firstEntry = true; 1348 SD_JOURNAL_FOREACH(journal.get()) 1349 { 1350 entryCount++; 1351 // Handle paging using skip (number of entries to skip from the 1352 // start) and top (number of entries to display) 1353 if (entryCount <= skip || entryCount > skip + top) 1354 { 1355 continue; 1356 } 1357 1358 std::string idStr; 1359 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1360 { 1361 continue; 1362 } 1363 1364 if (firstEntry) 1365 { 1366 firstEntry = false; 1367 } 1368 1369 logEntryArray.push_back({}); 1370 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 1371 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1372 bmcJournalLogEntry) != 0) 1373 { 1374 messages::internalError(asyncResp->res); 1375 return; 1376 } 1377 } 1378 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1379 if (skip + top < entryCount) 1380 { 1381 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1382 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1383 std::to_string(skip + top); 1384 } 1385 } 1386 }; 1387 1388 class BMCJournalLogEntry : public Node 1389 { 1390 public: 1391 BMCJournalLogEntry(CrowApp &app) : 1392 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1393 std::string()) 1394 { 1395 entityPrivileges = { 1396 {boost::beast::http::verb::get, {{"Login"}}}, 1397 {boost::beast::http::verb::head, {{"Login"}}}, 1398 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1399 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1400 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1401 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1402 } 1403 1404 private: 1405 void doGet(crow::Response &res, const crow::Request &req, 1406 const std::vector<std::string> ¶ms) override 1407 { 1408 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1409 if (params.size() != 1) 1410 { 1411 messages::internalError(asyncResp->res); 1412 return; 1413 } 1414 const std::string &entryID = params[0]; 1415 // Convert the unique ID back to a timestamp to find the entry 1416 uint64_t ts = 0; 1417 uint64_t index = 0; 1418 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1419 { 1420 return; 1421 } 1422 1423 sd_journal *journalTmp = nullptr; 1424 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1425 if (ret < 0) 1426 { 1427 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1428 messages::internalError(asyncResp->res); 1429 return; 1430 } 1431 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1432 journalTmp, sd_journal_close); 1433 journalTmp = nullptr; 1434 // Go to the timestamp in the log and move to the entry at the index 1435 // tracking the unique ID 1436 std::string idStr; 1437 bool firstEntry = true; 1438 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1439 for (uint64_t i = 0; i <= index; i++) 1440 { 1441 sd_journal_next(journal.get()); 1442 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1443 { 1444 messages::internalError(asyncResp->res); 1445 return; 1446 } 1447 if (firstEntry) 1448 { 1449 firstEntry = false; 1450 } 1451 } 1452 // Confirm that the entry ID matches what was requested 1453 if (idStr != entryID) 1454 { 1455 messages::resourceMissingAtURI(asyncResp->res, entryID); 1456 return; 1457 } 1458 1459 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1460 asyncResp->res.jsonValue) != 0) 1461 { 1462 messages::internalError(asyncResp->res); 1463 return; 1464 } 1465 } 1466 }; 1467 1468 class CrashdumpService : public Node 1469 { 1470 public: 1471 template <typename CrowApp> 1472 CrashdumpService(CrowApp &app) : 1473 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 1474 { 1475 entityPrivileges = { 1476 {boost::beast::http::verb::get, {{"Login"}}}, 1477 {boost::beast::http::verb::head, {{"Login"}}}, 1478 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1479 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1480 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1481 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1482 } 1483 1484 private: 1485 /** 1486 * Functions triggers appropriate requests on DBus 1487 */ 1488 void doGet(crow::Response &res, const crow::Request &req, 1489 const std::vector<std::string> ¶ms) override 1490 { 1491 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1492 // Copy over the static data to include the entries added by SubRoute 1493 asyncResp->res.jsonValue["@odata.id"] = 1494 "/redfish/v1/Systems/system/LogServices/Crashdump"; 1495 asyncResp->res.jsonValue["@odata.type"] = 1496 "#LogService.v1_1_0.LogService"; 1497 asyncResp->res.jsonValue["@odata.context"] = 1498 "/redfish/v1/$metadata#LogService.LogService"; 1499 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service"; 1500 asyncResp->res.jsonValue["Description"] = "Crashdump Service"; 1501 asyncResp->res.jsonValue["Id"] = "Crashdump"; 1502 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1503 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 1504 asyncResp->res.jsonValue["Entries"] = { 1505 {"@odata.id", 1506 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 1507 asyncResp->res.jsonValue["Actions"] = { 1508 {"Oem", 1509 {{"#Crashdump.OnDemand", 1510 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1511 "Actions/Oem/Crashdump.OnDemand"}}}}}}; 1512 1513 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 1514 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 1515 {"#Crashdump.SendRawPeci", 1516 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1517 "Actions/Oem/Crashdump.SendRawPeci"}}}); 1518 #endif 1519 } 1520 }; 1521 1522 class CrashdumpEntryCollection : public Node 1523 { 1524 public: 1525 template <typename CrowApp> 1526 CrashdumpEntryCollection(CrowApp &app) : 1527 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 1528 { 1529 entityPrivileges = { 1530 {boost::beast::http::verb::get, {{"Login"}}}, 1531 {boost::beast::http::verb::head, {{"Login"}}}, 1532 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1533 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1534 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1535 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1536 } 1537 1538 private: 1539 /** 1540 * Functions triggers appropriate requests on DBus 1541 */ 1542 void doGet(crow::Response &res, const crow::Request &req, 1543 const std::vector<std::string> ¶ms) override 1544 { 1545 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1546 // Collections don't include the static data added by SubRoute because 1547 // it has a duplicate entry for members 1548 auto getLogEntriesCallback = [asyncResp]( 1549 const boost::system::error_code ec, 1550 const std::vector<std::string> &resp) { 1551 if (ec) 1552 { 1553 if (ec.value() != 1554 boost::system::errc::no_such_file_or_directory) 1555 { 1556 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1557 << ec.message(); 1558 messages::internalError(asyncResp->res); 1559 return; 1560 } 1561 } 1562 asyncResp->res.jsonValue["@odata.type"] = 1563 "#LogEntryCollection.LogEntryCollection"; 1564 asyncResp->res.jsonValue["@odata.id"] = 1565 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 1566 asyncResp->res.jsonValue["@odata.context"] = 1567 "/redfish/v1/" 1568 "$metadata#LogEntryCollection.LogEntryCollection"; 1569 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 1570 asyncResp->res.jsonValue["Description"] = 1571 "Collection of Crashdump Entries"; 1572 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1573 logEntryArray = nlohmann::json::array(); 1574 for (const std::string &objpath : resp) 1575 { 1576 // Don't list the on-demand log 1577 if (objpath.compare(CrashdumpOnDemandPath) == 0) 1578 { 1579 continue; 1580 } 1581 std::size_t lastPos = objpath.rfind("/"); 1582 if (lastPos != std::string::npos) 1583 { 1584 logEntryArray.push_back( 1585 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" 1586 "Crashdump/Entries/" + 1587 objpath.substr(lastPos + 1)}}); 1588 } 1589 } 1590 asyncResp->res.jsonValue["Members@odata.count"] = 1591 logEntryArray.size(); 1592 }; 1593 crow::connections::systemBus->async_method_call( 1594 std::move(getLogEntriesCallback), 1595 "xyz.openbmc_project.ObjectMapper", 1596 "/xyz/openbmc_project/object_mapper", 1597 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1598 std::array<const char *, 1>{CrashdumpInterface}); 1599 } 1600 }; 1601 1602 std::string getLogCreatedTime(const nlohmann::json &Crashdump) 1603 { 1604 nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data"); 1605 if (cdIt != Crashdump.end()) 1606 { 1607 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO"); 1608 if (siIt != cdIt->end()) 1609 { 1610 nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); 1611 if (tsIt != siIt->end()) 1612 { 1613 const std::string *logTime = 1614 tsIt->get_ptr<const std::string *>(); 1615 if (logTime != nullptr) 1616 { 1617 return *logTime; 1618 } 1619 } 1620 } 1621 } 1622 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 1623 1624 return std::string(); 1625 } 1626 1627 class CrashdumpEntry : public Node 1628 { 1629 public: 1630 CrashdumpEntry(CrowApp &app) : 1631 Node(app, 1632 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", 1633 std::string()) 1634 { 1635 entityPrivileges = { 1636 {boost::beast::http::verb::get, {{"Login"}}}, 1637 {boost::beast::http::verb::head, {{"Login"}}}, 1638 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1639 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1640 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1641 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1642 } 1643 1644 private: 1645 void doGet(crow::Response &res, const crow::Request &req, 1646 const std::vector<std::string> ¶ms) override 1647 { 1648 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1649 if (params.size() != 1) 1650 { 1651 messages::internalError(asyncResp->res); 1652 return; 1653 } 1654 const int logId = std::atoi(params[0].c_str()); 1655 auto getStoredLogCallback = [asyncResp, logId]( 1656 const boost::system::error_code ec, 1657 const std::variant<std::string> &resp) { 1658 if (ec) 1659 { 1660 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1661 messages::internalError(asyncResp->res); 1662 return; 1663 } 1664 const std::string *log = std::get_if<std::string>(&resp); 1665 if (log == nullptr) 1666 { 1667 messages::internalError(asyncResp->res); 1668 return; 1669 } 1670 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1671 if (j.is_discarded()) 1672 { 1673 messages::internalError(asyncResp->res); 1674 return; 1675 } 1676 std::string t = getLogCreatedTime(j); 1677 asyncResp->res.jsonValue = { 1678 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1679 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1680 {"@odata.id", 1681 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 1682 std::to_string(logId)}, 1683 {"Name", "CPU Crashdump"}, 1684 {"Id", logId}, 1685 {"EntryType", "Oem"}, 1686 {"OemRecordFormat", "Intel Crashdump"}, 1687 {"Oem", {{"Intel", std::move(j)}}}, 1688 {"Created", std::move(t)}}; 1689 }; 1690 crow::connections::systemBus->async_method_call( 1691 std::move(getStoredLogCallback), CrashdumpObject, 1692 CrashdumpPath + std::string("/") + std::to_string(logId), 1693 "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface, 1694 "Log"); 1695 } 1696 }; 1697 1698 class OnDemandCrashdump : public Node 1699 { 1700 public: 1701 OnDemandCrashdump(CrowApp &app) : 1702 Node(app, 1703 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1704 "Crashdump.OnDemand/") 1705 { 1706 entityPrivileges = { 1707 {boost::beast::http::verb::get, {{"Login"}}}, 1708 {boost::beast::http::verb::head, {{"Login"}}}, 1709 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1710 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1711 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1712 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1713 } 1714 1715 private: 1716 void doPost(crow::Response &res, const crow::Request &req, 1717 const std::vector<std::string> ¶ms) override 1718 { 1719 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1720 static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher; 1721 1722 // Only allow one OnDemand Log request at a time 1723 if (onDemandLogMatcher != nullptr) 1724 { 1725 asyncResp->res.addHeader("Retry-After", "30"); 1726 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 1727 return; 1728 } 1729 // Make this static so it survives outside this method 1730 static boost::asio::steady_timer timeout(*req.ioService); 1731 1732 timeout.expires_after(std::chrono::seconds(30)); 1733 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 1734 onDemandLogMatcher = nullptr; 1735 if (ec) 1736 { 1737 // operation_aborted is expected if timer is canceled before 1738 // completion. 1739 if (ec != boost::asio::error::operation_aborted) 1740 { 1741 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 1742 } 1743 return; 1744 } 1745 BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log"; 1746 1747 messages::internalError(asyncResp->res); 1748 }); 1749 1750 auto onDemandLogMatcherCallback = [asyncResp]( 1751 sdbusplus::message::message &m) { 1752 BMCWEB_LOG_DEBUG << "OnDemand log available match fired"; 1753 timeout.cancel(); 1754 1755 sdbusplus::message::object_path objPath; 1756 boost::container::flat_map< 1757 std::string, boost::container::flat_map< 1758 std::string, std::variant<std::string>>> 1759 interfacesAdded; 1760 m.read(objPath, interfacesAdded); 1761 const std::string *log = std::get_if<std::string>( 1762 &interfacesAdded[CrashdumpInterface]["Log"]); 1763 if (log == nullptr) 1764 { 1765 messages::internalError(asyncResp->res); 1766 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1767 // match object inside which this lambda is executing. Once it 1768 // is set to nullptr, the match object will be destroyed and the 1769 // lambda will lose its context, including res, so it needs to 1770 // be the last thing done. 1771 onDemandLogMatcher = nullptr; 1772 return; 1773 } 1774 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1775 if (j.is_discarded()) 1776 { 1777 messages::internalError(asyncResp->res); 1778 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1779 // match object inside which this lambda is executing. Once it 1780 // is set to nullptr, the match object will be destroyed and the 1781 // lambda will lose its context, including res, so it needs to 1782 // be the last thing done. 1783 onDemandLogMatcher = nullptr; 1784 return; 1785 } 1786 std::string t = getLogCreatedTime(j); 1787 asyncResp->res.jsonValue = { 1788 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1789 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1790 {"Name", "CPU Crashdump"}, 1791 {"EntryType", "Oem"}, 1792 {"OemRecordFormat", "Intel Crashdump"}, 1793 {"Oem", {{"Intel", std::move(j)}}}, 1794 {"Created", std::move(t)}}; 1795 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1796 // match object inside which this lambda is executing. Once it is 1797 // set to nullptr, the match object will be destroyed and the lambda 1798 // will lose its context, including res, so it needs to be the last 1799 // thing done. 1800 onDemandLogMatcher = nullptr; 1801 }; 1802 onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 1803 *crow::connections::systemBus, 1804 sdbusplus::bus::match::rules::interfacesAdded() + 1805 sdbusplus::bus::match::rules::argNpath(0, 1806 CrashdumpOnDemandPath), 1807 std::move(onDemandLogMatcherCallback)); 1808 1809 auto generateonDemandLogCallback = 1810 [asyncResp](const boost::system::error_code ec, 1811 const std::string &resp) { 1812 if (ec) 1813 { 1814 if (ec.value() == 1815 boost::system::errc::operation_not_supported) 1816 { 1817 messages::resourceInStandby(asyncResp->res); 1818 } 1819 else 1820 { 1821 messages::internalError(asyncResp->res); 1822 } 1823 1824 timeout.cancel(); 1825 onDemandLogMatcher = nullptr; 1826 return; 1827 } 1828 }; 1829 crow::connections::systemBus->async_method_call( 1830 std::move(generateonDemandLogCallback), CrashdumpObject, 1831 CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog"); 1832 } 1833 }; 1834 1835 class SendRawPECI : public Node 1836 { 1837 public: 1838 SendRawPECI(CrowApp &app) : 1839 Node(app, 1840 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1841 "Crashdump.SendRawPeci/") 1842 { 1843 entityPrivileges = { 1844 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1845 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1846 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1847 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1848 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1849 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1850 } 1851 1852 private: 1853 void doPost(crow::Response &res, const crow::Request &req, 1854 const std::vector<std::string> ¶ms) override 1855 { 1856 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1857 uint8_t clientAddress = 0; 1858 uint8_t readLength = 0; 1859 std::vector<uint8_t> peciCommand; 1860 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1861 "ReadLength", readLength, "PECICommand", 1862 peciCommand)) 1863 { 1864 return; 1865 } 1866 1867 // Callback to return the Raw PECI response 1868 auto sendRawPECICallback = 1869 [asyncResp](const boost::system::error_code ec, 1870 const std::vector<uint8_t> &resp) { 1871 if (ec) 1872 { 1873 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 1874 << ec.message(); 1875 messages::internalError(asyncResp->res); 1876 return; 1877 } 1878 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 1879 {"PECIResponse", resp}}; 1880 }; 1881 // Call the SendRawPECI command with the provided data 1882 crow::connections::systemBus->async_method_call( 1883 std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath, 1884 CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength, 1885 peciCommand); 1886 } 1887 }; 1888 1889 /** 1890 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 1891 */ 1892 class DBusLogServiceActionsClear : public Node 1893 { 1894 public: 1895 DBusLogServiceActionsClear(CrowApp &app) : 1896 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 1897 "LogService.Reset") 1898 { 1899 entityPrivileges = { 1900 {boost::beast::http::verb::get, {{"Login"}}}, 1901 {boost::beast::http::verb::head, {{"Login"}}}, 1902 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1903 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1904 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1905 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1906 } 1907 1908 private: 1909 /** 1910 * Function handles POST method request. 1911 * The Clear Log actions does not require any parameter.The action deletes 1912 * all entries found in the Entries collection for this Log Service. 1913 */ 1914 void doPost(crow::Response &res, const crow::Request &req, 1915 const std::vector<std::string> ¶ms) override 1916 { 1917 BMCWEB_LOG_DEBUG << "Do delete all entries."; 1918 1919 auto asyncResp = std::make_shared<AsyncResp>(res); 1920 // Process response from Logging service. 1921 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 1922 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 1923 if (ec) 1924 { 1925 // TODO Handle for specific error code 1926 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 1927 asyncResp->res.result( 1928 boost::beast::http::status::internal_server_error); 1929 return; 1930 } 1931 1932 asyncResp->res.result(boost::beast::http::status::no_content); 1933 }; 1934 1935 // Make call to Logging service to request Clear Log 1936 crow::connections::systemBus->async_method_call( 1937 resp_handler, "xyz.openbmc_project.Logging", 1938 "/xyz/openbmc_project/logging", 1939 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 1940 } 1941 }; 1942 } // namespace redfish 1943