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