diff options
author | AppaRao Puli <apparao.puli@linux.intel.com> | 2020-06-13 16:31:29 +0300 |
---|---|---|
committer | AppaRao Puli <apparao.puli@linux.intel.com> | 2020-06-29 07:21:31 +0300 |
commit | 7f4eb5887f9a52a2832ee9b6e06749575903128a (patch) | |
tree | 687e4aa39ced8c2164e9518df87cecf713acd0b9 /redfish-core | |
parent | 4cde5d90f690fad956407fb759c03c09da002026 (diff) | |
download | bmcweb-7f4eb5887f9a52a2832ee9b6e06749575903128a.tar.xz |
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>
Diffstat (limited to 'redfish-core')
-rw-r--r-- | redfish-core/include/event_service_manager.hpp | 458 |
1 files changed, 457 insertions, 1 deletions
diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp index fb0cfbc518..c2d6d383bf 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* metricReportFormatType = "MetricReport"; 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 @@ class Subscription 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 @@ class EventServiceManager initConfig(); } + std::string lastEventTStr; size_t noOfEventLogSubscribers; size_t noOfMetricReportSubscribers; std::shared_ptr<sdbusplus::bus::match::match> matchTelemetryMonitor; @@ -493,6 +783,13 @@ class EventServiceManager { updateSubscriptionData(); } + +#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES + if (lastEventTStr.empty()) + { + cacheLastEventTimestamp(); + } +#endif return id; } @@ -555,6 +852,165 @@ class EventServiceManager } } +#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 @@ class EventServiceManager } return true; } -}; +}; // namespace redfish } // namespace redfish |