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