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