Revert "Revert "EventService: Add event log support with inotify""
This reverts commit 29d2a95ba12f8b5abed040df7fd59790d6ba2517.
Enable EventService back by fixing issue with
not having '/var/log/redfish' file.
Fix is at: https://gerrit.openbmc-project.xyz/#/c/openbmc/bmcweb/+/33639/
Tested:
- Along with above mentioned change, removed
'/var/log/redfish' file and restarted bmcweb. It works.
Change-Id: Ia908bbdf5b9a643afee212a526074f62372208dc
Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
index fb0cfbc..c2d6d38 100644
--- a/redfish-core/include/event_service_manager.hpp
+++ b/redfish-core/include/event_service_manager.hpp
@@ -15,6 +15,11 @@
*/
#pragma once
#include "node.hpp"
+#include "registries.hpp"
+#include "registries/base_message_registry.hpp"
+#include "registries/openbmc_message_registry.hpp"
+
+#include <sys/inotify.h>
#include <boost/asio/io_context.hpp>
#include <boost/container/flat_map.hpp>
@@ -41,6 +46,224 @@
static constexpr const char* eventServiceFile =
"/var/lib/bmcweb/eventservice_config.json";
+#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
+constexpr const char* redfishEventLogFile = "/var/log/redfish";
+constexpr const uint32_t inotifyFileAction = IN_MODIFY;
+std::shared_ptr<boost::asio::posix::stream_descriptor> inotifyConn = nullptr;
+
+// <ID, timestamp, RedfishLogId, registryPrefix, MessageId, MessageArgs>
+using EventLogObjectsType =
+ std::tuple<std::string, std::string, std::string, std::string, std::string,
+ boost::beast::span<std::string>>;
+
+namespace message_registries
+{
+static const Message*
+ getMsgFromRegistry(const std::string& messageKey,
+ const boost::beast::span<const MessageEntry>& registry)
+{
+ boost::beast::span<const MessageEntry>::const_iterator messageIt =
+ std::find_if(registry.cbegin(), registry.cend(),
+ [&messageKey](const MessageEntry& messageEntry) {
+ return !messageKey.compare(messageEntry.first);
+ });
+ if (messageIt != registry.cend())
+ {
+ return &messageIt->second;
+ }
+
+ return nullptr;
+}
+
+static const Message* formatMessage(const std::string_view& messageID)
+{
+ // Redfish MessageIds are in the form
+ // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
+ // the right Message
+ std::vector<std::string> fields;
+ fields.reserve(4);
+ boost::split(fields, messageID, boost::is_any_of("."));
+ if (fields.size() != 4)
+ {
+ return nullptr;
+ }
+ std::string& registryName = fields[0];
+ std::string& messageKey = fields[3];
+
+ // Find the right registry and check it for the MessageKey
+ if (std::string(base::header.registryPrefix) == registryName)
+ {
+ return getMsgFromRegistry(
+ messageKey, boost::beast::span<const MessageEntry>(base::registry));
+ }
+ if (std::string(openbmc::header.registryPrefix) == registryName)
+ {
+ return getMsgFromRegistry(
+ messageKey,
+ boost::beast::span<const MessageEntry>(openbmc::registry));
+ }
+ return nullptr;
+}
+} // namespace message_registries
+
+namespace event_log
+{
+bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
+ const bool firstEntry = true)
+{
+ static time_t prevTs = 0;
+ static int index = 0;
+ if (firstEntry)
+ {
+ prevTs = 0;
+ }
+
+ // Get the entry timestamp
+ std::time_t curTs = 0;
+ std::tm timeStruct = {};
+ std::istringstream entryStream(logEntry);
+ if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
+ {
+ curTs = std::mktime(&timeStruct);
+ if (curTs == -1)
+ {
+ return false;
+ }
+ }
+ // If the timestamp isn't unique, increment the index
+ index = (curTs == prevTs) ? index + 1 : 0;
+
+ // Save the timestamp
+ prevTs = curTs;
+
+ entryID = std::to_string(curTs);
+ if (index > 0)
+ {
+ entryID += "_" + std::to_string(index);
+ }
+ return true;
+}
+
+int getEventLogParams(const std::string& logEntry, std::string& timestamp,
+ std::string& messageID,
+ boost::beast::span<std::string>& messageArgs)
+{
+ // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
+ // First get the Timestamp
+ size_t space = logEntry.find_first_of(" ");
+ if (space == std::string::npos)
+ {
+ return -EINVAL;
+ }
+ timestamp = logEntry.substr(0, space);
+ // Then get the log contents
+ size_t entryStart = logEntry.find_first_not_of(" ", space);
+ if (entryStart == std::string::npos)
+ {
+ return -EINVAL;
+ }
+ std::string_view entry(logEntry);
+ entry.remove_prefix(entryStart);
+ // Use split to separate the entry into its fields
+ std::vector<std::string> logEntryFields;
+ boost::split(logEntryFields, entry, boost::is_any_of(","),
+ boost::token_compress_on);
+ // We need at least a MessageId to be valid
+ if (logEntryFields.size() < 1)
+ {
+ return -EINVAL;
+ }
+ messageID = logEntryFields[0];
+
+ // Get the MessageArgs from the log if there are any
+ if (logEntryFields.size() > 1)
+ {
+ std::string& messageArgsStart = logEntryFields[1];
+ // If the first string is empty, assume there are no MessageArgs
+ std::size_t messageArgsSize = 0;
+ if (!messageArgsStart.empty())
+ {
+ messageArgsSize = logEntryFields.size() - 1;
+ }
+
+ messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize);
+ }
+
+ return 0;
+}
+
+void getRegistryAndMessageKey(const std::string& messageID,
+ std::string& registryName,
+ std::string& messageKey)
+{
+ // Redfish MessageIds are in the form
+ // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
+ // the right Message
+ std::vector<std::string> fields;
+ fields.reserve(4);
+ boost::split(fields, messageID, boost::is_any_of("."));
+ if (fields.size() == 4)
+ {
+ registryName = fields[0];
+ messageKey = fields[3];
+ }
+}
+
+int formatEventLogEntry(const std::string& logEntryID,
+ const std::string& messageID,
+ const boost::beast::span<std::string>& messageArgs,
+ std::string timestamp, const std::string customText,
+ nlohmann::json& logEntryJson)
+{
+ // Get the Message from the MessageRegistry
+ const message_registries::Message* message =
+ message_registries::formatMessage(messageID);
+
+ std::string msg;
+ std::string severity;
+ if (message != nullptr)
+ {
+ msg = message->message;
+ severity = message->severity;
+ }
+
+ // Fill the MessageArgs into the Message
+ int i = 0;
+ for (const std::string& messageArg : messageArgs)
+ {
+ std::string argStr = "%" + std::to_string(++i);
+ size_t argPos = msg.find(argStr);
+ if (argPos != std::string::npos)
+ {
+ msg.replace(argPos, argStr.length(), messageArg);
+ }
+ }
+
+ // Get the Created time from the timestamp. The log timestamp is in
+ // RFC3339 format which matches the Redfish format except for the
+ // fractional seconds between the '.' and the '+', so just remove them.
+ std::size_t dot = timestamp.find_first_of(".");
+ std::size_t plus = timestamp.find_first_of("+");
+ if (dot != std::string::npos && plus != std::string::npos)
+ {
+ timestamp.erase(dot, plus - dot);
+ }
+
+ // Fill in the log entry with the gathered data
+ logEntryJson = {{"EventId", logEntryID},
+ {"EventType", "Event"},
+ {"Severity", std::move(severity)},
+ {"Message", std::move(msg)},
+ {"MessageId", std::move(messageID)},
+ {"MessageArgs", std::move(messageArgs)},
+ {"EventTimestamp", std::move(timestamp)},
+ {"Context", customText}};
+ return 0;
+}
+
+} // namespace event_log
+#endif
+
class Subscription
{
public:
@@ -112,6 +335,72 @@
this->eventSeqNum++;
}
+#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
+ void filterAndSendEventLogs(
+ const std::vector<EventLogObjectsType>& eventRecords)
+ {
+ nlohmann::json logEntryArray;
+ for (const EventLogObjectsType& logEntry : eventRecords)
+ {
+ const std::string& idStr = std::get<0>(logEntry);
+ const std::string& timestamp = std::get<1>(logEntry);
+ const std::string& messageID = std::get<2>(logEntry);
+ const std::string& registryName = std::get<3>(logEntry);
+ const std::string& messageKey = std::get<4>(logEntry);
+ const boost::beast::span<std::string>& messageArgs =
+ std::get<5>(logEntry);
+
+ // If registryPrefixes list is empty, don't filter events
+ // send everything.
+ if (registryPrefixes.size())
+ {
+ auto obj = std::find(registryPrefixes.begin(),
+ registryPrefixes.end(), registryName);
+ if (obj == registryPrefixes.end())
+ {
+ continue;
+ }
+ }
+
+ // If registryMsgIds list is empty, don't filter events
+ // send everything.
+ if (registryMsgIds.size())
+ {
+ auto obj = std::find(registryMsgIds.begin(),
+ registryMsgIds.end(), messageKey);
+ if (obj == registryMsgIds.end())
+ {
+ continue;
+ }
+ }
+
+ logEntryArray.push_back({});
+ nlohmann::json& bmcLogEntry = logEntryArray.back();
+ if (event_log::formatEventLogEntry(idStr, messageID, messageArgs,
+ timestamp, customText,
+ bmcLogEntry) != 0)
+ {
+ BMCWEB_LOG_DEBUG << "Read eventLog entry failed";
+ continue;
+ }
+ }
+
+ if (logEntryArray.size() < 1)
+ {
+ BMCWEB_LOG_DEBUG << "No log entries available to be transferred.";
+ return;
+ }
+
+ nlohmann::json msg = {{"@odata.type", "#Event.v1_4_0.Event"},
+ {"Id", std::to_string(eventSeqNum)},
+ {"Name", "Event Log"},
+ {"Events", logEntryArray}};
+
+ this->sendEvent(msg.dump());
+ this->eventSeqNum++;
+ }
+#endif
+
void filterAndSendReports(const std::string& id,
const std::string& readingsTs,
const ReadingsObjType& readings)
@@ -189,6 +478,7 @@
initConfig();
}
+ std::string lastEventTStr;
size_t noOfEventLogSubscribers;
size_t noOfMetricReportSubscribers;
std::shared_ptr<sdbusplus::bus::match::match> matchTelemetryMonitor;
@@ -493,6 +783,13 @@
{
updateSubscriptionData();
}
+
+#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
+ if (lastEventTStr.empty())
+ {
+ cacheLastEventTimestamp();
+ }
+#endif
return id;
}
@@ -555,6 +852,165 @@
}
}
+#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
+ void cacheLastEventTimestamp()
+ {
+ std::ifstream logStream(redfishEventLogFile);
+ if (!logStream.good())
+ {
+ BMCWEB_LOG_ERROR << " Redfish log file open failed \n";
+ return;
+ }
+ std::string logEntry;
+ while (std::getline(logStream, logEntry))
+ {
+ size_t space = logEntry.find_first_of(" ");
+ if (space == std::string::npos)
+ {
+ // Shouldn't enter here but lets skip it.
+ BMCWEB_LOG_DEBUG << "Invalid log entry found.";
+ continue;
+ }
+ lastEventTStr = logEntry.substr(0, space);
+ }
+ BMCWEB_LOG_DEBUG << "Last Event time stamp set: " << lastEventTStr;
+ }
+
+ void readEventLogsFromFile()
+ {
+ if (!serviceEnabled || !noOfEventLogSubscribers)
+ {
+ BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions.";
+ return;
+ }
+ std::ifstream logStream(redfishEventLogFile);
+ if (!logStream.good())
+ {
+ BMCWEB_LOG_ERROR << " Redfish log file open failed";
+ return;
+ }
+
+ std::vector<EventLogObjectsType> eventRecords;
+
+ bool startLogCollection = false;
+ bool firstEntry = true;
+
+ std::string logEntry;
+ while (std::getline(logStream, logEntry))
+ {
+ if (!startLogCollection)
+ {
+ if (boost::starts_with(logEntry, lastEventTStr))
+ {
+ startLogCollection = true;
+ }
+ continue;
+ }
+
+ std::string idStr;
+ if (!event_log::getUniqueEntryID(logEntry, idStr, firstEntry))
+ {
+ continue;
+ }
+ firstEntry = false;
+
+ std::string timestamp;
+ std::string messageID;
+ boost::beast::span<std::string> messageArgs;
+ if (event_log::getEventLogParams(logEntry, timestamp, messageID,
+ messageArgs) != 0)
+ {
+ BMCWEB_LOG_DEBUG << "Read eventLog entry params failed";
+ continue;
+ }
+
+ std::string registryName;
+ std::string messageKey;
+ event_log::getRegistryAndMessageKey(messageID, registryName,
+ messageKey);
+ if (registryName.empty() || messageKey.empty())
+ {
+ continue;
+ }
+
+ lastEventTStr = timestamp;
+ eventRecords.emplace_back(idStr, timestamp, messageID, registryName,
+ messageKey, messageArgs);
+ }
+
+ for (const auto& it : this->subscriptionsMap)
+ {
+ std::shared_ptr<Subscription> entry = it.second;
+ if (entry->eventFormatType == "Event")
+ {
+ entry->filterAndSendEventLogs(eventRecords);
+ }
+ }
+ }
+
+ static void watchRedfishEventLogFile()
+ {
+ if (inotifyConn == nullptr)
+ {
+ return;
+ }
+
+ static std::array<char, 1024> readBuffer;
+
+ inotifyConn->async_read_some(
+ boost::asio::buffer(readBuffer),
+ [&](const boost::system::error_code& ec,
+ const std::size_t& bytesTransferred) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Callback Error: " << ec.message();
+ return;
+ }
+ std::size_t index = 0;
+ while ((index + sizeof(inotify_event)) <= bytesTransferred)
+ {
+ struct inotify_event event;
+ std::memcpy(&event, &readBuffer[index],
+ sizeof(inotify_event));
+ if (event.mask == inotifyFileAction)
+ {
+ EventServiceManager::getInstance()
+ .readEventLogsFromFile();
+ }
+ index += (sizeof(inotify_event) + event.len);
+ }
+
+ watchRedfishEventLogFile();
+ });
+ }
+
+ static int startEventLogMonitor(boost::asio::io_context& ioc)
+ {
+ inotifyConn =
+ std::make_shared<boost::asio::posix::stream_descriptor>(ioc);
+ int fd = inotify_init1(IN_NONBLOCK);
+ if (fd == -1)
+ {
+ BMCWEB_LOG_ERROR << "inotify_init1 failed.";
+ return -1;
+ }
+ auto wd = inotify_add_watch(fd, redfishEventLogFile, inotifyFileAction);
+ if (wd == -1)
+ {
+ BMCWEB_LOG_ERROR
+ << "inotify_add_watch failed for redfish log file.";
+ return -1;
+ }
+
+ // monitor redfish event log file
+ inotifyConn->assign(fd);
+ watchRedfishEventLogFile();
+
+ return 0;
+ }
+
+#endif
+
void getMetricReading(const std::string& service,
const std::string& objPath, const std::string& intf)
{
@@ -706,6 +1162,6 @@
}
return true;
}
-};
+}; // namespace redfish
} // namespace redfish
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index 738fb28..11e8e92 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -118,6 +118,15 @@
redfish::RedfishService redfish(app);
+#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
+ int rc = redfish::EventServiceManager::startEventLogMonitor(*io);
+ if (rc)
+ {
+ BMCWEB_LOG_ERROR << "Redfish event handler setup failed...";
+ return rc;
+ }
+#endif
+
app.run();
io->run();