1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2020 Intel Corporation 4 #include "event_log.hpp" 5 6 #include "logging.hpp" 7 #include "registries.hpp" 8 #include "str_utility.hpp" 9 10 #include <nlohmann/json.hpp> 11 12 #include <cerrno> 13 #include <cstddef> 14 #include <cstdint> 15 #include <ctime> 16 #include <iomanip> 17 #include <span> 18 #include <sstream> 19 #include <string> 20 #include <string_view> 21 #include <utility> 22 #include <vector> 23 24 namespace redfish 25 { 26 27 namespace event_log 28 { 29 30 bool getUniqueEntryID(const std::string& logEntry, std::string& entryID) 31 { 32 static time_t prevTs = 0; 33 static int index = 0; 34 35 // Get the entry timestamp 36 std::time_t curTs = 0; 37 std::tm timeStruct = {}; 38 std::istringstream entryStream(logEntry); 39 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 40 { 41 curTs = std::mktime(&timeStruct); 42 if (curTs == -1) 43 { 44 return false; 45 } 46 } 47 // If the timestamp isn't unique, increment the index 48 index = (curTs == prevTs) ? index + 1 : 0; 49 50 // Save the timestamp 51 prevTs = curTs; 52 53 entryID = std::to_string(curTs); 54 if (index > 0) 55 { 56 entryID += "_" + std::to_string(index); 57 } 58 return true; 59 } 60 61 int getEventLogParams(const std::string& logEntry, std::string& timestamp, 62 std::string& messageID, 63 std::vector<std::string>& messageArgs) 64 { 65 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 66 // First get the Timestamp 67 size_t space = logEntry.find_first_of(' '); 68 if (space == std::string::npos) 69 { 70 BMCWEB_LOG_ERROR("EventLog Params: could not find first space: {}", 71 logEntry); 72 return -EINVAL; 73 } 74 timestamp = logEntry.substr(0, space); 75 // Then get the log contents 76 size_t entryStart = logEntry.find_first_not_of(' ', space); 77 if (entryStart == std::string::npos) 78 { 79 BMCWEB_LOG_ERROR("EventLog Params: could not find log contents: {}", 80 logEntry); 81 return -EINVAL; 82 } 83 std::string_view entry(logEntry); 84 entry.remove_prefix(entryStart); 85 // Use split to separate the entry into its fields 86 std::vector<std::string> logEntryFields; 87 bmcweb::split(logEntryFields, entry, ','); 88 // We need at least a MessageId to be valid 89 if (logEntryFields.empty()) 90 { 91 BMCWEB_LOG_ERROR("EventLog Params: could not find entry fields: {}", 92 logEntry); 93 return -EINVAL; 94 } 95 messageID = logEntryFields[0]; 96 97 // Get the MessageArgs from the log if there are any 98 if (logEntryFields.size() > 1) 99 { 100 const std::string& messageArgsStart = logEntryFields[1]; 101 // If the first string is empty, assume there are no MessageArgs 102 if (!messageArgsStart.empty()) 103 { 104 messageArgs.assign(logEntryFields.begin() + 1, 105 logEntryFields.end()); 106 } 107 } 108 109 return 0; 110 } 111 112 int formatEventLogEntry(uint64_t eventId, const std::string& logEntryID, 113 const std::string& messageID, 114 const std::span<std::string_view> messageArgs, 115 std::string timestamp, const std::string& customText, 116 nlohmann::json::object_t& logEntryJson) 117 { 118 // Get the Message from the MessageRegistry 119 const registries::Message* message = registries::getMessage(messageID); 120 121 if (message == nullptr) 122 { 123 BMCWEB_LOG_DEBUG( 124 "{}: could not find messageID '{}' for log entry {} in registry", 125 __func__, messageID, logEntryID); 126 return -1; 127 } 128 129 std::string msg = 130 redfish::registries::fillMessageArgs(messageArgs, message->message); 131 if (msg.empty()) 132 { 133 BMCWEB_LOG_DEBUG("{}: message is empty after filling fillMessageArgs", 134 __func__); 135 return -1; 136 } 137 138 // Get the Created time from the timestamp. The log timestamp is in 139 // RFC3339 format which matches the Redfish format except for the 140 // fractional seconds between the '.' and the '+', so just remove them. 141 std::size_t dot = timestamp.find_first_of('.'); 142 std::size_t plus = timestamp.find_first_of('+', dot); 143 if (dot != std::string::npos && plus != std::string::npos) 144 { 145 timestamp.erase(dot, plus - dot); 146 } 147 148 // Fill in the log entry with the gathered data 149 logEntryJson["EventId"] = std::to_string(eventId); 150 151 logEntryJson["Severity"] = message->messageSeverity; 152 logEntryJson["Message"] = std::move(msg); 153 logEntryJson["MessageId"] = messageID; 154 logEntryJson["MessageArgs"] = messageArgs; 155 logEntryJson["EventTimestamp"] = std::move(timestamp); 156 logEntryJson["Context"] = customText; 157 return 0; 158 } 159 160 } // namespace event_log 161 162 } // namespace redfish 163