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