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