psutil: Add PSU Event Log Reporting Methods

This commit introduces new methods in the Updater class to log errors
and create Platform Event Logs for PSU failures and I2C-related
issues. The following methods are added:

- createServiceableEventLog:
  Creates a serviceable Platform Event Log using
  xyz.openbmc_project.Logging.Create. Takes an error name, severity, and
  additional data as parameters. Retrieves the logging service and calls
  the D-Bus method to create the log.

- getI2CAdditionalData:
  Retrieves I2C-related callout data, including I2C bus ID, address, and
  error number. Formats the ID and address as hexadecimal strings and
  returns them as a map.

- callOutI2CEventLog:
  Reports a Event Log for I2C failures. Collects PSU inventory path,
  priority, and I2C-specific callout data. Merges any additional
  provided data and creates a Event Log.

- callOutPsuEventLog:
  Reports a Event Log for general PSU failures. Includes PSU inventory
  callout information and priority.

- callOutSWEventLog:
  Reports Event Log for software-related PSU file issues.Logs errors
  using predefined PSU firmware file issue messages. These changes
  improve fault logging and troubleshooting capabilities in PSU
  management by ensuring proper logging and event recording.

- callOutGoodEventLog:
  Reports a successful PSU firmware update Event Log along with the
  firmware level.

- Added accessor functions to provide control over Event logging,
  allowing:
  - Enabling/disabling Event logging at runtime.
  - Tracking if Event Log  has been logged in the current session.

  Accessor functions:
    - enableEventLogging()
    - disableEventLogging()
    - isEventLogEnabled()
    - enableEventLoggedThisSession()
    - isEventLoggedThisSession()

Test:
 Verified each function correctly generates Event Log  in openBmc. The
 test was conducted by writing a standalone test program that:
 1 - Calls createServiceableEventLog() with various error names,
 severity levels, and additionalData then check the generated Event Log.
 2 - Calls getI2CAdditionalData() and verifies the returned data
 contains the correct I2C bus ID, Address and error number.
 3 - Calls callOutI2CEventLog() with simulated I2C error and checks the
 generated Event Log.
 4 - Calls callOutPsuEventLog() and callOutSWEventLog() with different
 input data and verifies the correct error messages are logged.

Change-Id: Id52b3e1c0b2a3b09ae3ac5d93bc53e02d335d6c7
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/tools/power-utils/updater.cpp b/tools/power-utils/updater.cpp
index 7da839f..16e225f 100644
--- a/tools/power-utils/updater.cpp
+++ b/tools/power-utils/updater.cpp
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #include "updater.hpp"
 
 #include "aei_updater.hpp"
@@ -20,13 +21,16 @@
 #include "types.hpp"
 #include "utility.hpp"
 #include "utils.hpp"
+#include "version.hpp"
 
 #include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Logging/Create/client.hpp>
 
 #include <chrono>
 #include <fstream>
-#include <thread>
-#include <vector>
+#include <iostream>
+#include <map>
+#include <string>
 
 using namespace phosphor::logging;
 namespace util = phosphor::power::util;
@@ -169,7 +173,7 @@
     }
     catch (const std::ios_base::failure& e)
     {
-        lg2::error("Error reading firmware: {ERROR}", "ERROR", e.what());
+        lg2::error("Error reading firmware: {ERROR}", "ERROR", e);
         readDataBytes.clear();
     }
     return readDataBytes;
@@ -220,7 +224,7 @@
     catch (const fs::filesystem_error& e)
     {
         lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}",
-                   "PATH", devPath, "ERR", e.what());
+                   "PATH", devPath, "ERR", e);
     }
 }
 
@@ -380,4 +384,105 @@
     auto [id, addr] = utils::parseDeviceName(devName);
     i2c = i2c::create(id, addr);
 }
+
+void Updater::createServiceableEventLog(
+    const std::string& errorName, const std::string& severity,
+    std::map<std::string, std::string>& additionalData)
+{
+    if (!isEventLogEnabled() || isEventLoggedThisSession())
+    {
+        return;
+    }
+
+    using namespace sdbusplus::xyz::openbmc_project;
+    using LoggingCreate =
+        sdbusplus::client::xyz::openbmc_project::logging::Create<>;
+    enableEventLoggedThisSession();
+    try
+    {
+        additionalData["_PID"] = std::to_string(getpid());
+        auto method = bus.new_method_call(LoggingCreate::default_service,
+                                          LoggingCreate::instance_path,
+                                          LoggingCreate::interface, "Create");
+        method.append(errorName, severity, additionalData);
+
+        bus.call(method);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        lg2::error(
+            "Failed creating event log for fault {ERROR_NAME}, error {ERR}",
+            "ERROR_NAME", errorName, "ERR", e);
+    }
+    disableEventLogging();
+}
+
+std::map<std::string, std::string> Updater::getI2CAdditionalData()
+{
+    std::map<std::string, std::string> additionalData;
+    auto [id, addr] = utils::parseDeviceName(getDevName());
+    std::string hexIdString = std::format("0x{:x}", id);
+    std::string hexAddrString = std::format("0x{:x}", addr);
+
+    additionalData["CALLOUT_IIC_BUS"] = hexIdString;
+    additionalData["CALLOUT_IIC_ADDR"] = hexAddrString;
+    return additionalData;
+}
+
+/*
+ * callOutI2CEventLog calls out FRUs in the following order:
+ * 1 - PSU  high priority
+ * 2 - CALLOUT_IIC_BUS
+ */
+void Updater::callOutI2CEventLog(
+    std::map<std::string, std::string> extraAdditionalData,
+    const std::string& exceptionString, const int errorCode)
+{
+    std::map<std::string, std::string> additionalData = {
+        {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}};
+    additionalData.merge(extraAdditionalData);
+    additionalData.merge(getI2CAdditionalData());
+    additionalData["CALLOUT_ERRNO"] = std::to_string(errorCode);
+    if (!exceptionString.empty())
+    {
+        additionalData["I2C_EXCEPTION"] = exceptionString;
+    }
+    createServiceableEventLog(FW_UPDATE_FAILED_MSG, ERROR_SEVERITY,
+                              additionalData);
+}
+
+/*
+ * callOutPsuEventLog calls out PSU and system planar
+ */
+void Updater::callOutPsuEventLog(
+    std::map<std::string, std::string> extraAdditionalData)
+{
+    std::map<std::string, std::string> additionalData = {
+        {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}};
+    additionalData.merge(extraAdditionalData);
+    createServiceableEventLog(updater::FW_UPDATE_FAILED_MSG,
+                              updater::ERROR_SEVERITY, additionalData);
+}
+
+/*
+ * callOutSWEventLog calls out the BMC0001 procedure.
+ */
+void Updater::callOutSWEventLog(
+    std::map<std::string, std::string> additionalData)
+{
+    createServiceableEventLog(updater::PSU_FW_FILE_ISSUE_MSG,
+                              updater::ERROR_SEVERITY, additionalData);
+}
+
+/*
+ * callOutGoodEventLog calls out a successful firmware update.
+ */
+void Updater::callOutGoodEventLog()
+{
+    std::map<std::string, std::string> additionalData = {
+        {"SUCCESSFUL_PSU_UPDATE", getPsuInventoryPath()},
+        {"FIRMWARE_VERSION", version::getVersion(bus, getPsuInventoryPath())}};
+    createServiceableEventLog(updater::FW_UPDATE_SUCCESS_MSG,
+                              updater::INFORMATIONAL_SEVERITY, additionalData);
+}
 } // namespace updater