regulators: Create error_logging_utils

Create the error_logging_utils namespace for utility functions that make
it easier to log errors.  Create two initial functions within the
namespace.

The first function logs an error based on an exception.  The exception
may have one or more nested inner exceptions.  The function finds the
highest priority exception and logs a corresponding error.

The second function provides the same basic behavior as the first, but
it adds an ErrorHistory parameter.  An error will only be logged if it
was not previously logged.  The ErrorHistory object is used to
determine whether an error has been previously logged.  This avoids
logging duplicate errors if a regulator operation is occurring
repeatedly, such as reading sensor values.

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: If246cde9a0f60c5bba34ae4a0d68fb511c0024fc
diff --git a/phosphor-regulators/src/error_logging_utils.cpp b/phosphor-regulators/src/error_logging_utils.cpp
new file mode 100644
index 0000000..fb3506e
--- /dev/null
+++ b/phosphor-regulators/src/error_logging_utils.cpp
@@ -0,0 +1,195 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "error_logging_utils.hpp"
+
+#include "config_file_parser_error.hpp"
+#include "exception_utils.hpp"
+#include "i2c_interface.hpp"
+#include "journal.hpp"
+#include "pmbus_error.hpp"
+#include "write_verification_error.hpp"
+
+#include <sdbusplus/exception.hpp>
+
+#include <vector>
+
+namespace phosphor::power::regulators::error_logging_utils
+{
+
+void logError(std::exception_ptr eptr, Entry::Level severity,
+              Services& services)
+{
+    // Specify empty error history so that all error types will be logged
+    ErrorHistory history{};
+    logError(eptr, severity, services, history);
+}
+
+void logError(std::exception_ptr eptr, Entry::Level severity,
+              Services& services, ErrorHistory& history)
+{
+    // Check for null exception pointer
+    if (!eptr)
+    {
+        return;
+    }
+
+    // Get exception to log from specified exception and any nested exceptions
+    std::exception_ptr exceptionToLog = internal::getExceptionToLog(eptr);
+
+    // Log an error based on the exception
+    ErrorLogging& errorLogging = services.getErrorLogging();
+    Journal& journal = services.getJournal();
+    ErrorType errorType{};
+    try
+    {
+        std::rethrow_exception(exceptionToLog);
+    }
+    catch (const ConfigFileParserError& e)
+    {
+        errorType = ErrorType::configFile;
+        if (!history.wasLogged(errorType))
+        {
+            history.setWasLogged(errorType, true);
+            errorLogging.logConfigFileError(severity, journal);
+        }
+    }
+    catch (const PMBusError& e)
+    {
+        errorType = ErrorType::pmbus;
+        if (!history.wasLogged(errorType))
+        {
+            history.setWasLogged(errorType, true);
+            errorLogging.logPMBusError(severity, journal, e.getInventoryPath());
+        }
+    }
+    catch (const WriteVerificationError& e)
+    {
+        errorType = ErrorType::writeVerification;
+        if (!history.wasLogged(errorType))
+        {
+            history.setWasLogged(errorType, true);
+            errorLogging.logWriteVerificationError(severity, journal,
+                                                   e.getInventoryPath());
+        }
+    }
+    catch (const i2c::I2CException& e)
+    {
+        errorType = ErrorType::i2c;
+        if (!history.wasLogged(errorType))
+        {
+            history.setWasLogged(errorType, true);
+            errorLogging.logI2CError(severity, journal, e.bus, e.addr,
+                                     e.errorCode);
+        }
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        errorType = ErrorType::dbus;
+        if (!history.wasLogged(errorType))
+        {
+            history.setWasLogged(errorType, true);
+            errorLogging.logDBusError(severity, journal);
+        }
+    }
+    catch (const std::exception& e)
+    {
+        errorType = ErrorType::internal;
+        if (!history.wasLogged(errorType))
+        {
+            history.setWasLogged(errorType, true);
+            errorLogging.logInternalError(severity, journal);
+        }
+    }
+    catch (...)
+    {
+        errorType = ErrorType::internal;
+        if (!history.wasLogged(errorType))
+        {
+            history.setWasLogged(errorType, true);
+            errorLogging.logInternalError(severity, journal);
+        }
+    }
+}
+
+namespace internal
+{
+
+std::exception_ptr getExceptionToLog(std::exception_ptr eptr)
+{
+    // Default to selecting the outermost exception
+    std::exception_ptr exceptionToLog{eptr};
+
+    // Get vector containing this exception and any nested exceptions
+    std::vector<std::exception_ptr> exceptions =
+        exception_utils::getExceptions(eptr);
+
+    // Define temporary constants for exception priorities
+    const int lowPriority{0}, mediumPriority{1}, highPriority{2};
+
+    // Loop through the exceptions from innermost to outermost.  Find the
+    // exception with the highest priority.  If there is a tie, select the
+    // outermost exception with that priority.
+    int highestPriorityFound{-1};
+    for (std::exception_ptr curptr : exceptions)
+    {
+        int priority{-1};
+        try
+        {
+            std::rethrow_exception(curptr);
+        }
+        catch (const ConfigFileParserError& e)
+        {
+            priority = highPriority;
+        }
+        catch (const PMBusError& e)
+        {
+            priority = highPriority;
+        }
+        catch (const WriteVerificationError& e)
+        {
+            priority = highPriority;
+        }
+        catch (const i2c::I2CException& e)
+        {
+            priority = highPriority;
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            priority = mediumPriority;
+        }
+        catch (const std::exception& e)
+        {
+            priority = lowPriority;
+        }
+        catch (...)
+        {
+            priority = lowPriority;
+        }
+
+        if (priority >= highestPriorityFound)
+        {
+            highestPriorityFound = priority;
+            exceptionToLog = curptr;
+        }
+    }
+
+    return exceptionToLog;
+}
+
+} // namespace internal
+
+} // namespace phosphor::power::regulators::error_logging_utils
diff --git a/phosphor-regulators/src/error_logging_utils.hpp b/phosphor-regulators/src/error_logging_utils.hpp
new file mode 100644
index 0000000..d90b2df
--- /dev/null
+++ b/phosphor-regulators/src/error_logging_utils.hpp
@@ -0,0 +1,85 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "error_history.hpp"
+#include "error_logging.hpp"
+#include "services.hpp"
+
+#include <exception>
+
+/**
+ * @namespace error_logging_utils
+ *
+ * Contains utility functions for logging errors.
+ */
+namespace phosphor::power::regulators::error_logging_utils
+{
+
+/**
+ * Logs an error based on the specified exception and any nested inner
+ * exceptions.
+ *
+ * @param eptr exception pointer
+ * @param severity severity level
+ * @param services system services like error logging and the journal
+ */
+void logError(std::exception_ptr eptr, Entry::Level severity,
+              Services& services);
+
+/**
+ * Logs an error, if necessary, based on the specified exception and any nested
+ * inner exceptions.
+ *
+ * Finds the error type would be logged based on the specified exception and any
+ * nested inner exceptions.
+ *
+ * Checks to see if this error type has already been logged according to the
+ * specified ErrorHistory object.
+ *
+ * If the error type has not been logged, an error log entry is created, and the
+ * ErrorHistory is updated.
+ *
+ * If the error type has been logged, no further action is taken.
+ *
+ * @param eptr exception pointer
+ * @param severity severity level
+ * @param services system services like error logging and the journal
+ * @param history error logging history
+ */
+void logError(std::exception_ptr eptr, Entry::Level severity,
+              Services& services, ErrorHistory& history);
+
+/*
+ * Internal implementation details
+ */
+namespace internal
+{
+
+/**
+ * Returns the exception to use when logging an error.
+ *
+ * Inspects the specified exception and any nested inner exceptions.  Returns
+ * the highest priority exception from an error logging perspective.
+ *
+ * @param eptr exception pointer
+ * @return exception to log
+ */
+std::exception_ptr getExceptionToLog(std::exception_ptr eptr);
+
+} // namespace internal
+
+} // namespace phosphor::power::regulators::error_logging_utils
diff --git a/phosphor-regulators/src/meson.build b/phosphor-regulators/src/meson.build
index 9993591..40cb3e8 100644
--- a/phosphor-regulators/src/meson.build
+++ b/phosphor-regulators/src/meson.build
@@ -10,6 +10,7 @@
     'configuration.cpp',
     'device.cpp',
     'error_logging.cpp',
+    'error_logging_utils.cpp',
     'exception_utils.cpp',
     'ffdc_file.cpp',
     'id_map.cpp',
diff --git a/phosphor-regulators/test/error_logging_utils_tests.cpp b/phosphor-regulators/test/error_logging_utils_tests.cpp
new file mode 100644
index 0000000..f7665c0
--- /dev/null
+++ b/phosphor-regulators/test/error_logging_utils_tests.cpp
@@ -0,0 +1,719 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config_file_parser_error.hpp"
+#include "error_logging.hpp"
+#include "error_logging_utils.hpp"
+#include "i2c_interface.hpp"
+#include "journal.hpp"
+#include "mock_error_logging.hpp"
+#include "mock_journal.hpp"
+#include "mock_services.hpp"
+#include "pmbus_error.hpp"
+#include "write_verification_error.hpp"
+
+#include <errno.h>
+
+#include <sdbusplus/exception.hpp>
+
+#include <exception>
+#include <filesystem>
+#include <stdexcept>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::regulators;
+
+namespace fs = std::filesystem;
+
+using ::testing::Ref;
+
+TEST(ErrorLoggingUtilsTests, LogError_3Parameters)
+{
+    // Create exception with two nesting levels; top priority is inner
+    // PMBusError
+    std::exception_ptr eptr;
+    try
+    {
+        try
+        {
+            throw PMBusError{"VOUT_MODE contains unsupported data format",
+                             "reg1",
+                             "/xyz/openbmc_project/inventory/system/chassis/"
+                             "motherboard/reg1"};
+        }
+        catch (...)
+        {
+            std::throw_with_nested(
+                std::runtime_error{"Unable to set output voltage"});
+        }
+    }
+    catch (...)
+    {
+        eptr = std::current_exception();
+    }
+
+    // Create MockServices.  Expect logPMBusError() to be called.
+    MockServices services{};
+    MockErrorLogging& errorLogging = services.getMockErrorLogging();
+    MockJournal& journal = services.getMockJournal();
+    EXPECT_CALL(
+        errorLogging,
+        logPMBusError(
+            Entry::Level::Error, Ref(journal),
+            "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1"))
+        .Times(1);
+
+    // Log error based on the nested exception
+    error_logging_utils::logError(eptr, Entry::Level::Error, services);
+}
+
+TEST(ErrorLoggingUtilsTests, LogError_4Parameters)
+{
+    // Test where exception pointer is null
+    {
+        std::exception_ptr eptr;
+
+        // Create MockServices.  Don't expect any log*() methods to be called.
+        MockServices services{};
+
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Error, services,
+                                      history);
+    }
+
+    // Test where exception is not nested
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
+                                    0x30, ENODEV};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logI2CError() to be called.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging,
+                    logI2CError(Entry::Level::Critical, Ref(journal),
+                                "/dev/i2c-8", 0x30, ENODEV))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Critical, services,
+                                      history);
+    }
+
+    // Test where exception is nested
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            try
+            {
+                throw std::invalid_argument{"JSON element is not an array"};
+            }
+            catch (...)
+            {
+                std::throw_with_nested(ConfigFileParserError{
+                    fs::path{"/etc/phosphor-regulators/config.json"},
+                    "Unable to parse JSON configuration file"});
+            }
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logConfigFileError() to be called.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging,
+                    logConfigFileError(Entry::Level::Warning, Ref(journal)))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Warning, services,
+                                      history);
+    }
+
+    // Test where exception is a ConfigFileParserError
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            throw ConfigFileParserError{
+                fs::path{"/etc/phosphor-regulators/config.json"},
+                "Unable to parse JSON configuration file"};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logConfigFileError() to be called once.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging,
+                    logConfigFileError(Entry::Level::Error, Ref(journal)))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Error, services,
+                                      history);
+
+        // Try to log error again.  Should not happen due to ErrorHistory.
+        error_logging_utils::logError(eptr, Entry::Level::Error, services,
+                                      history);
+    }
+
+    // Test where exception is a PMBusError
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            throw PMBusError{"VOUT_MODE contains unsupported data format",
+                             "reg1",
+                             "/xyz/openbmc_project/inventory/system/chassis/"
+                             "motherboard/reg1"};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logPMBusError() to be called once.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging,
+                    logPMBusError(Entry::Level::Error, Ref(journal),
+                                  "/xyz/openbmc_project/inventory/system/"
+                                  "chassis/motherboard/reg1"))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Error, services,
+                                      history);
+
+        // Try to log error again.  Should not happen due to ErrorHistory.
+        error_logging_utils::logError(eptr, Entry::Level::Error, services,
+                                      history);
+    }
+
+    // Test where exception is a WriteVerificationError
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            throw WriteVerificationError{
+                "value_written: 0xDEAD, value_read: 0xBEEF", "reg1",
+                "/xyz/openbmc_project/inventory/system/chassis/motherboard/"
+                "reg1"};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logWriteVerificationError() to be
+        // called once.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging, logWriteVerificationError(
+                                      Entry::Level::Warning, Ref(journal),
+                                      "/xyz/openbmc_project/inventory/system/"
+                                      "chassis/motherboard/reg1"))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Warning, services,
+                                      history);
+
+        // Try to log error again.  Should not happen due to ErrorHistory.
+        error_logging_utils::logError(eptr, Entry::Level::Warning, services,
+                                      history);
+    }
+
+    // Test where exception is a I2CException
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
+                                    0x30, ENODEV};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logI2CError() to be called once.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging,
+                    logI2CError(Entry::Level::Informational, Ref(journal),
+                                "/dev/i2c-8", 0x30, ENODEV))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Informational,
+                                      services, history);
+
+        // Try to log error again.  Should not happen due to ErrorHistory.
+        error_logging_utils::logError(eptr, Entry::Level::Informational,
+                                      services, history);
+    }
+
+    // Test where exception is a sdbusplus::exception_t
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            // Throw InvalidEnumString; exception_t is a pure virtual base class
+            throw sdbusplus::exception::InvalidEnumString{};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logDBusError() to be called once.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging,
+                    logDBusError(Entry::Level::Debug, Ref(journal)))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Debug, services,
+                                      history);
+
+        // Try to log error again.  Should not happen due to ErrorHistory.
+        error_logging_utils::logError(eptr, Entry::Level::Debug, services,
+                                      history);
+    }
+
+    // Test where exception is a std::exception
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            throw std::runtime_error{
+                "Unable to read configuration file: No such file or directory"};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logInternalError() to be called once.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging,
+                    logInternalError(Entry::Level::Error, Ref(journal)))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Error, services,
+                                      history);
+
+        // Try to log error again.  Should not happen due to ErrorHistory.
+        error_logging_utils::logError(eptr, Entry::Level::Error, services,
+                                      history);
+    }
+
+    // Test where exception is unknown type
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            throw 23;
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Create MockServices.  Expect logInternalError() to be called once.
+        MockServices services{};
+        MockErrorLogging& errorLogging = services.getMockErrorLogging();
+        MockJournal& journal = services.getMockJournal();
+        EXPECT_CALL(errorLogging,
+                    logInternalError(Entry::Level::Warning, Ref(journal)))
+            .Times(1);
+
+        // Log error based on the nested exception
+        ErrorHistory history{};
+        error_logging_utils::logError(eptr, Entry::Level::Warning, services,
+                                      history);
+
+        // Try to log error again.  Should not happen due to ErrorHistory.
+        error_logging_utils::logError(eptr, Entry::Level::Warning, services,
+                                      history);
+    }
+}
+
+TEST(ErrorLoggingUtilsTests, GetExceptionToLog)
+{
+    // Test where exception is not nested
+    {
+        std::exception_ptr eptr;
+        try
+        {
+            throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
+                                    0x30, ENODEV};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(eptr);
+        EXPECT_EQ(eptr, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is innermost exception
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw PMBusError{
+                    "VOUT_MODE contains unsupported data format", "reg1",
+                    "/xyz/openbmc_project/inventory/system/chassis/"
+                    "motherboard/reg1"};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(
+                    std::runtime_error{"Unable to set output voltage"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(inner, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is middle exception
+    {
+        std::exception_ptr inner, middle, outer;
+        try
+        {
+            try
+            {
+                try
+                {
+                    throw std::invalid_argument{"JSON element is not an array"};
+                }
+                catch (...)
+                {
+                    inner = std::current_exception();
+                    std::throw_with_nested(ConfigFileParserError{
+                        fs::path{"/etc/phosphor-regulators/config.json"},
+                        "Unable to parse JSON configuration file"});
+                }
+            }
+            catch (...)
+            {
+                middle = std::current_exception();
+                std::throw_with_nested(
+                    std::runtime_error{"Unable to load config file"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(middle, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is outermost exception
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw std::invalid_argument{"JSON element is not an array"};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(ConfigFileParserError{
+                    fs::path{"/etc/phosphor-regulators/config.json"},
+                    "Unable to parse JSON configuration file"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(outer, exceptionToLog);
+    }
+
+    // Test where exception is nested: Two exceptions have same priority.
+    // Should return outermost exception with that priority.
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw std::invalid_argument{"JSON element is not an array"};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(
+                    std::runtime_error{"Unable to load config file"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(outer, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is ConfigFileParserError
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw ConfigFileParserError{
+                    fs::path{"/etc/phosphor-regulators/config.json"},
+                    "Unable to parse JSON configuration file"};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(
+                    std::runtime_error{"Unable to load config file"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(inner, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is PMBusError
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw std::invalid_argument{"Invalid VOUT_MODE value"};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(PMBusError{
+                    "VOUT_MODE contains unsupported data format", "reg1",
+                    "/xyz/openbmc_project/inventory/system/chassis/motherboard/"
+                    "reg1"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(outer, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is
+    // WriteVerificationError
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw WriteVerificationError{
+                    "value_written: 0xDEAD, value_read: 0xBEEF", "reg1",
+                    "/xyz/openbmc_project/inventory/system/chassis/motherboard/"
+                    "reg1"};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(
+                    std::runtime_error{"Unable set voltage"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(inner, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is I2CException
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw std::invalid_argument{"No such device"};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(i2c::I2CException{
+                    "Unable to open device reg1", "/dev/i2c-8", 0x30, ENODEV});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(outer, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is
+    // sdbusplus::exception_t
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                // Throw InvalidEnumString; exception_t is pure virtual class
+                throw sdbusplus::exception::InvalidEnumString{};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(
+                    std::runtime_error{"Unable to call D-Bus method"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(inner, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is std::exception
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw std::invalid_argument{"No such file or directory"};
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(
+                    std::runtime_error{"Unable load config file"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(outer, exceptionToLog);
+    }
+
+    // Test where exception is nested: Highest priority is unknown type
+    {
+        std::exception_ptr inner, outer;
+        try
+        {
+            try
+            {
+                throw 23;
+            }
+            catch (...)
+            {
+                inner = std::current_exception();
+                std::throw_with_nested(std::string{"Unable load config file"});
+            }
+        }
+        catch (...)
+        {
+            outer = std::current_exception();
+        }
+
+        std::exception_ptr exceptionToLog =
+            error_logging_utils::internal::getExceptionToLog(outer);
+        EXPECT_EQ(outer, exceptionToLog);
+    }
+}
diff --git a/phosphor-regulators/test/meson.build b/phosphor-regulators/test/meson.build
index b36ff1a..0fd55de 100644
--- a/phosphor-regulators/test/meson.build
+++ b/phosphor-regulators/test/meson.build
@@ -10,6 +10,7 @@
     'configuration_tests.cpp',
     'device_tests.cpp',
     'error_history_tests.cpp',
+    'error_logging_utils_tests.cpp',
     'exception_utils_tests.cpp',
     'ffdc_file_tests.cpp',
     'id_map_tests.cpp',