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