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