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